토비의 스프링 3장
3. 템플릿
템플릿이란 바뀌는 성질이 다른 코드 중에서 변경이 거의 일어나지 않으며 일정한 패턴으로 유지되는 특성을 가진 부분을 자유롭게 변경되는 성질을 가진 부분으로부터 독립시켜서 효과적으로 활용할 수 있는 방법
3.1 다시 보는 초난감 DAO
- 3.1.1 예외처리 기능을 갖춘 DAO
- JDBC 수정 기능의 예외처리 코드
- Connection과 PreparedStatement를 가져와서 사용하지만 예외가 발생했을 때 리소스가 제대로 반환되지 않는 문제가 있다.
- try catch finally를 이용하여 해결
- 하지만 close에서도 SQLException이 발생할 수 있기 때문에 try-catch가 또 필요하다.
- JDBC 조회 기능의 예외처리
- ResultSet도 반환이 필요
- JDBC 수정 기능의 예외처리 코드
3.2 변하는 것과 변하지 않는 것
3.2.1 JDBC try/catch/finally 코드의 문제점
- try/catch/finally가 너무 많아 실수할 가능성이 높음
3.2.2 분리와 재사용을 위한 디자인 패턴 적용
- 메소드 추출
- 자주 바뀌는 부분을 메소드로 독립시켰는데 확장돼야 하는 부분이 분리되어 이득이 없다.
- 템플릿 메소드 패턴의 적용
- 변하지 않는 부분은 슈퍼클래스에 두고 변하는 부분은 추상 메소드로 정의해둬서 서브클래스에서 오버라이드하여 새롭게 정의하여 쓴다.
- 하지만 템플릿 메소드 패턴으로의 접근은 DAO 로직마다 상속을 통해 새로운 클래스를 만들어야 한다는 문제가 있다.
- 전략 패턴의 적용
- 오브젝트를 아예 둘로 분리하고 클래스 레벨에서는 인터페이스를 통해서만 의존하도록 만드는 전략 패턴 사용
- 전략 패턴은 필요에 따라 컨텍스트는 그대로 유지되면서 전략을 바꿔 쓸 수 있다는 것인데, 컨텍스트 안에서 이미 구체적인 전략 클래스를 사용하도록 고정되기 때문에 문제가 있다.
1 2 3
public interface StatementStrategy { PreparedStatement makePreparedStatement(Conncetion c) throws SQLException; }
1 2 3 4 5 6
public class DeleteAllStatement implements StatementStrategy { public PreparedStatement makePreparedStatement(Connection c) throws SQLException { PreparedStatement ps = c.prepareStatement("delete from users"); return ps; } }
- DI 적용을 위한 클라이언트/컨텍스트 분리
- 전략 패턴에 따르면 Context가 어떤 전략을 사용하게 할 것인가는 Context를 사용하는 앞단의 Client가 결정하는 게 일반적이다.
- 클라이언트가 컨텍스트가 사용할 전략을 정해서 전달하기 때문에 DI구조
- 메소드 추출
3.3 JDBC 전략 패턴의 최적화
- 3.3.1 전략 클래스의 추가 정보
- add()에도 전략 패턴을 적용, User 정보가 필요하기 때문에 StatementStrategy를 상속받은 AddStatement에서 User에 관한 정보를 생성자로부터 제공받는다.
- 3.3.2 전략과 클라이언트의 동거
- 현재 DAO 메소드마다 새로운 StatementStrategy 구현 클래스를 만들어야 한다는 문제가 있다.
- 그리고 전달할 부가적인 정보가 있는 경우, 이를 위해 오브젝트를 전달받는 생성자와 오브젝트 변수가 필요하다는 문제가 있다.
- 로컬 클래스
- add() 메소드 내의 로컬 클래스로 AddStatement를 이전한다.
- 이렇게 하면 클래스 파일을 줄일 수 있고, 정보에 직접 접근할 수 있다.
- 익명 내부 클래스
- 익명 내부 클래스: 클래스 선언과 오브젝트 생성이 결합된 형태로 만들어지며, 상속할 클래스나 구현할 인터페이스를 생성자 대신 사용한다.
- new 인터페이스이름() { 클래스 본문 };
- 익명 내부 클래스: 클래스 선언과 오브젝트 생성이 결합된 형태로 만들어지며, 상속할 클래스나 구현할 인터페이스를 생성자 대신 사용한다.
3.4 컨텍스트와 DI
- 3.4.1 JdbcContext의 분리
- 클래스 분리
- 빈 의존관계 변경
- 3.4.2 JdbcContext의 특별한 DI
- 스프링 빈으로 DI
- JdbcContext를 UserDao와 DI 구조로 만들어야 할 이유
- JdbcContext가 스프링 컨테이너의 싱글톤 레지스트리에서 관리되는 싱글톤 빈이기 때문에
- JdbcContext가 DI를 통해 다른 빈에 의존하고 있기 때문에
- 왜 인터페이스를 사용하지 않았을까?
- UserDao와 JdbcContext가 매우 긴밀한 관계를 가지고 강하게 결합되어 있기 때문에
- JdbcContext를 UserDao와 DI 구조로 만들어야 할 이유
- 코드를 이용하는 수동 DI
- UserDao에서 dataSource를 주입받아 jdbcContext에 수동으로 주입한다.
- 이 방법을 사용하면 굳이 인터페이스를 두지 않아도 될 만큼 긴밀한 관계를 갖는 DAO클래스와 JdbcContext를 어색하게 따로 빈으로 분리하지 않고 내부에서 직접 만들어 사용하면서도 다른 오브젝트에 대한 DI를 적용할 수 있다는 점이다.
- 스프링 빈으로 DI
3.5 템플릿과 콜백
템플릿/콜백 패턴: 전략패턴의 기본 구조에 익명 내부 클래스를 활용한 방식
- 템플릿: 고정된 틀 안에 바꿀 수 있는 부분을 넣어서 사용하는 경우
콜백: 실행되는 것을 목적으로 다른 오브젝트의 메소드에 전달되는 오브젝트
- 3.5.1 템플릿/콜백의 작동원리
- 템플릿/콜백의 특징
- 클라이언트의 역할은 템플릿 안에서 실행될 로직을 담은 콜백 오브젝트를 만들고, 콜백이 참조할 정보를 제공하는 것이다. 만들어진 콜백은 클라이언트가 템플릿의 메소드를 호출할 때 파라미터로 전달된다.
- 템플릿은 정해진 작업 흐름을 따라 작업을 진행하다가 내부에서 생성한 참조정보를 가지고 콜백 오브젝트의 메소드를 호출한다. 콜백은 클라이언트 메소드에 있는 정보와 템플릿이 제공한 참조정보를 이용해서 작업을 수행하고 그 결과를 다시 템플릿에 돌려준다.
- 템플릿은 콜백이 돌려준 정보를 사용해서 작업을 마저 수행한다. 경우에 따라 최종 결과를 클라이언트에 다시 돌려주기도 한다.
- 템플릿/콜백의 특징
- 3.5.2 편리한 콜백의 재활용 DAO 메소드에서 매번 익명 내부 클래스를 사용하기 때문에 상대적으로 코드를 작성하고 읽기가 조금 불편하다는 단점이 있다.
- 콜백의 분리와 재활용 SQL 부분만 바뀌기 때문에 SQL문장만 파라미터로 받도록 변경
- 콜백과 템플릿의 결합 일반적으로 성격이 다른 코드들은 가능한 한 분리하는 편이 낫지만, 이 경우에는 하나의 목적을 위해 서로 긴밀하게 연관되어 동작하는 응집력이 강한 코드들이기 때문에 한 군데 모여 있는 게 유리하다.
- 3.5.3 템플릿/콜백의 응용
- 제네릭스 응용: 타입이 다양하게 바뀔 수 있다면 사용
3.6 스프링의 JdbcTemplate
- 3.6.1 update()
- 3.6.2 queryForInt()
- 3.6.3 queryForObject()
- 3.6.4 query()
- 테스트 보완
- getAll()에 대한 네거티브 테스트 필요
- 테스트 보완
- 3.6.5 재사용 가능한 콜백의 분리
- DI를 위한 코드정리
- 중복 제거
- 템플릿/콜백 패턴과 UserDao
- UserDao의 개선점
- userMapper가 인스턴스 변수로 설정되어 있고, 한 번 만들어지면 변경되지 않는 프로퍼티와 같은 성격을 띠고 있으니 아ㅖ UserDao 빈의 DI용 프로퍼티로 만든다.
- DAO 메소드에서 사용하는 SQL문장을 UserDao 코드가 아니라 외부 리소스에 담고 이를 읽어와 사용
- UserDao의 개선점
This post is licensed under CC BY 4.0 by the author.