3.1 다시 보는 초난감 DAO
예외 처리
try/catch/finally
- try: 예외가 발생할 수 있는 코드
- catch: 예외가 발생했을 때 부가적인 작업을 하기 위한 코드
- finally: 예외 발생과는 별개로 반드시 실행되는 코드
3.2 변하는 것과 변하지 않는 것
try/catch/finally의 문제점
- try/catch/finally가 제대로 작성되지 않으면 커넥션 리소스가 반환되지 않는 경우가 방생하게 된다.
- 이렇게 리소스가 반환되지 않는 상황이 누적된다면 서버가 중단되게 된다.
- 이 try/catch/finally 코드를 일일히 확인하는 것은 매우 번거로운 일이므로 분리해낼 필요가 있다.
분리와 재사용을 위한 디자인 패턴 적용
템플릿 메소드 패턴의 적용
- 변하는 부분을 메소드 추출을 통해 얻어내도록 한다(makeStatement)
- 이 메소드를 추상 메소드로 구현하고 해당 클래스도 추상 클래스로 구현한다.
- 추상 클래스를 상속받아 기능에 따라 makeStatement 메소드를 구현하여 사용한다.
- 추출해야 하는 메소드(기능)이 많을 경우 해당 메소드의 수 만큼 서브클래스를 구현해야하는 단점이 있다.
전략 패턴의 적용
- 개방 폐쇄 원칙을 잘 지키도록 하기 위해 오브젝트를 컨텍스트와 전략으로 분리한다.
- 컨텍스트는 전략과는 관계 없이 동일하게 동작하는 부분이다.
- 전략은 기능마다 다르게 확장되는 부분으로 예시에서는 PreparedStatement를 생성해주는 기능이다.
DI 적용
- 컨텍스트가 전략 클래스의 어떤 오브젝트가 사용됬는지 알고 있는것은 OCP에 어긋나므로 DI를 적용할 필요가 있다.
- 클라이언트에서 전략을 생성하고 주입해주는 방법을 통해 컨텍스트는 어떤 전략 클래스의 오브젝트가 사용되는지 알 필요가 없게 된다.
3.3 JDBC 전략 패턴의 최적화
전략과 클라이언트의 동거
개선할 부분
- DAO 메소드 마다 새로운 StatementStrategy 구현 클래스를 생성해야 한다.
- DAO 메소드에서 StatementStrategy에 전달할 부가적인 정보가 있을 경우 번거롭게 인스턴스 변수를 생성해야 한다.
로컬 클래스
- 전략 클래스를 매번 독립된 클래스 파일로 생성하지 않고 UserDao의 내부 클래스로 정의한다.
- 전략 마다 많은 클래스 파일을 생성하지 않아도 된다.
- 내부 클래스는 자신이 선언된 곳의 정보에 접근할 수 있기 때문에 부자적인 정보를 전달하기 위한 변수 생성을 하지 않아도 된다.
3.4 컨텍스트와 DI
JdbcContext의 분리
- 현재 컨텍스트 메소드는 UserDao 내의 PreparedeStatement를 실행하기 위한 메소드이다.
- 이 메소드를 다른 DAO에서도 사용할 수 있게 UserDao 클래스 밖으로 독립시킨다.
클래스 분리
- JdbcContext 클래스를 생성하여 datasource에 의존하도록 한다.
- workWithStatementStrategy를 통해 Stratey를 파라미터로 전달받는다.
- UserDao는 private 변수 jdbcContext를 갖고 이 오브젝트를 이용한다.
- JdbcContext는 추상 클래스가 아니라 일반적인 DI의 방법과는 다르지만, JdbcContext의 구현 방법이 바뀌지 않으므로 구체 클래스로 사용한다.
스프링 빈으로 DI
- JdbcContetxt를 구체 클래스로 사용하지 않았지만, DI가 필요한 두 가지 이유가 있다.
- 싱글톤 레지스트리에서 관리되는 싱글톤 빈이 되기 위해서이다.
- JdbcContext가 다른 빈에(DataSource) 의존하고 있기 때문이다.
- DI를 위해서는 주입과 관련된 양 쪽 오브젝트 모두 스프링 빈에 등록되어야한다.
3.5 템플릿과 콜백
템플릿
- 어떤 목적을 위해 미리 만들어둔 틀
- 전략 패턴의 컨텍스트를 의미한다.
콜백
- 실행되는 것을 목적으로 다른 오브젝트의 메소드에 전될되는 오브젝트
- 전략 패턴의 내부 클래스로 만들어지는 오브젝트를 의미한다.
3.6 스프링의 JdbcTemplate
update()
- SQL 구문을 파라미터로 받아 실행하는 메소드이다.
- 치환자를 가진 SQL 구문을 만들수 있으며 가변인자는 파라미터와 순서대로 바인딩되어 사용된다.
queryForInt()
- Integer 타입의 결과를 가져오는 SQL 구문을 파라미터로 받아 사용한다.
queryObject()
- SQL 구문과 Object[] 배열을 파라미터로 받아 사용한다.
- queryForObject()의 파라미터 SQL을 실행하면 한 개의 로우만 얻을 것이라고 기대한다.
- 로우의 개수가 하나가 아닌 경우 예외를(EmptyResultDataAccessException) 던지도록 만들어져있다.
query()
- queryForObject() 와는 다르게 여러 개의 로우가 결과로 나오는 경우 사용한다.
- 리턴 타입은
List<T>
이다.
- 첫 번째 파라미터는 실행할 SQL 쿼리이다.
- 두 번째 파라미터는 바인딩할 파라미터이다.
- 마지막 파라미터는 RowMapper 콜백이다.
- RowMapper는 SQL 쿼리의 결과를 정해진 클래스 타입의 오브젝트에 매핑한다.