토비의 스프링 [3장 - 3.3장] 스터디

25636 단어 toby-springtoby-spring

템플릿이란, 바뀌는 성질이 다른 코드 중에서 변경이 거의 일어나지 않으며 일정한 패턴으로 유지되는 특성을 가진 부분을 자유롭게 변경되는 성질을 가진 부분으로부터 독립시켜서 효과적으로 활용할 수 있도록 하는 기법이다.

3.1. 예외처리로 알아보는 템플릿 기법

3.1.1. 예외처리 기능을 갖춘 DAO

JDBC 수정 기능의 예외처리 코드

public void deleteAll() throws SQLExcetion {
	Connection c = dataSource.getConnection();

	PreparedStatement ps = c.prepareStatement("delete from users");
	ps.executeUpdate();

	ps.close();
	c.close();
}

리소스 반환과 close()
Connection, PreparedStatement에는 close()메소드가 있다.
리소스를 반환한다는 개념으로서, ps, connection이 보통 풀 방식으로 운영되므로 해당 풀을 반환시켜줌으로써 전체 허용된 연결 수 중 일부를 다시 사용할 수 있게끔 한다.
close() 메소드를 사용하여 DB 풀을 반환하지 않을 경우에는, 장기간 서비스가 실행 시 허용된 연결을 초과할 경우 커넥션이 어려워지므로, 서비스가 마비되는 이슈가 있을 수 있다.

이런 JDBC 코드에서는 어떤 상황에서도 가져온 리소스를 반환하도록 try-catch-final 구문 사용을 권장한다.

예외 발생 시에도 리소스를 반환하도록 수정한 deleteAll()

public void deleteAll() throws SQLException {
	Connection c = null;
	PreparedStatement ps = null;

	try {
		c = dataSource.getConnection();
		ps = c.preparedStatement("delete from users");
		ps.executeUpdate();
	} catch (SQLException e) {
		throw e;
	} finally {
		if (ps != null) {
			try {
				ps.close();
			} catch (SQLException e) {}
		}

		if (c != null) {
			try {
				c.close();
			} catch (SQLException e) {}
		}
	}
}

3.2. 변하는 것과 변하지 않는 것

3.2.1. JDBC try-catch-finally 코드의 문제점

복잡한 UserDao가 되어 try-catch-fianlly 블록이 매 메소드마다 반복적으로 나오게 된다.

3.2.2. 분리와 재사용을 위한 디자인 패턴 적용

deleteAll() Method

변하지 않는 부분과 변하는 부분을 극명하게 가를 수 있다.

만약 add() 메소드라면 위 그림에서 변하는 부분을 아래와 같이 변경하면 된다.

...
ps = c.preparetStatement("insert into users(id, name, password) values(?, ?, ?)");

ps.setString(1, user.getId());
ps.setString(2, user.getName());
ps.setString(3, user.getPassword());
...
템플릿 메소드 패턴 적용

템플릿 메소드 패턴은 상속을 통해 기능을 확장해서 사용하는 부분이다.
변하지 않는 부분은 슈퍼클래스에 두고 변하는 부분은 추상 메소드로 정의해둬서 서브클래스에서 오버라이드하여 새롭게 정의해 쓰도록 하는 것이다.

makeStatement()를 구현한 UserDao 서브클래스

public class UserDaoDeleteAll extends UserDao {
	protected PreparedStatement makeStatement(Connection c) throws SQLException {
		PreparedStatement ps = c.preparedStatement("delete from users");
		return ps;
	}
}

위와 같은 구성은 개방 폐쇄 원칙을 지키긴 하지만 템플릿 메소드 패턴은 근본적으로 접근에 제한이 많다.
DAO 로직마다 상속을 통해 새로운 클래스를 만들어야 한다.

또한 확장구조가 이미 클래스 설꼐 시점에서 고정되어 버린다!!!

변하지 않는 코드를 가진 UserDao의 JDBC try-catch-finally 블록과 변하는 PreparedStatement를 담고 있는 서브클래스들이 이미 클래스 레벨에서 컴파일 시점에 이미 그 관계가 설정되어 있다!!
따라사 관계에 대한 유연성이 떨어져 버린다.

전략 패턴 적용

개방 폐쇄 원칙을 잘 지키는 구조이면서 유연하고 확장성이 뛰어난 패턴이다.

오브젝트를 아예 둘로 분리하고 클래스 레벨에서는 인터페이스를 통해서만 의존하도록 만드는 패턴이다.
전략 패턴은 OCP관점에 보면 확장에 해당하는 변하는 부분을 별도의 클래스로 만들어 추상화된 인터페이스를 통해 위임하는 형식이다.

deleteAll() 메소드에서 변하지 않는 부분이라고 명시한 것이 바로 이 contextMethod()가 된다.
deleteAll()은 JDBC를 이용해 DB를 업데이트하는 작업이라는 변하지 않는 맥락(Context)를 가진다.

- DB 커넥션 가져오기
- PreparedStatement를 만들어줄 외부 기능 호출하기
- 전달받은 PreparedStatement 실행하기
- 예외가 발생하면 이를 다시 메소드 밖으로 던지기
- 모든 경우에 만들어진 PreparedStatement와 Connection을 적절히 닫아주기

두 번째 작업에서 사용하는 PreparedStatement를 만들어주는 외부 기능이 전략 패턴에서 말하는 전략이라고 볼 수 있다.

StatementStrategy 인터페이스

public interface StatementStrategy {
	PreparedStatement makePreparedStatement(Connection c) throws SQLException;
}

deleteAll() 메소드의 기능을 구현한 StatementStrategy 전략 클래스

public class DeleteAllStatement implements StatementStrategy {
	public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
		PreparedStatement ps = c.preparedStatement("delete from users");
		return ps;
	}
}

메소드로 분리한 try-catch-finally 컨텍스트 코드

public void jdbcContextWithStatementStrategy(StatementStrategy stmt) throws SQLException {
	Connection c = null;
	PreparedStatement ps = null;

	try {
		c = dataSoruce.getConnection();
		ps = stmt.makePreparedStatement(c);

		ps.executeUpdate();
	} catch (SQLException e) {
		throws e;
	} finally {
		if (ps != null) { try { ps.close(); } catch (SQLException e) {}
		if (c != null) { try { c.close(); } catch (SQLException e) {}
	}
	
}

클라이언트 책임을 담당할 deleteAll() 메소드

public void deleteAll() throws SQLException {
	StatementStrategy st = new DeleteALlStatement();
	jdbcContextWithStatement(st);
}

3.3. JDBC 전략 패턴의 최적화

3.3.1. 전략 클래스의 추가 정보

add() 메소드의 PreparedStatement 생성 로직을 분리한 클래스

public class AddStatement implements StatementStrategy {
	User user;

	public AddStatement(User user) {
		this.user = user;
	}

	public PreparedStatement makePreparedStatement(Connection c) throws SQLException {
		PreparedStatement ps = c.preparedStatement("insert into users(id, name, password) values(?, ?, ?)");
		ps.setString(1, user.getId());
		ps.setString(2, user.getName());
		ps.setString(3, user.getPassword());

		return ps;
	}
}

user 정보를 AddStatement에 전달해주는 add() 메소드

public void add (User user) throws SQLException {
	StatementStrategy st = new AddStatement(user);
	jdbcContextWithStatementStrategy(st);
}

최종 add() 메소드

public void add(final User user) throws SQLException {
	jdbcContextWithStatementStrategy(
		new StatementStrategy() {
			public PreparedStatetment makePreparedStatemnt(Connection c) throws SQLException {
				PreparedStatement("insert into users(id, name, password) values(?, ?, ?)");
				ps.setString(1, user.getId());
				ps.setString(2, user.getName());
				ps.setString(3, user.getPassword());

				return ps;
			}
		}
	);
}

좋은 웹페이지 즐겨찾기