지금까지 기존의 deleteAll() 메소드에 담겨 있던 변하지 않는 부분, 자주 변하는 부분을 전략 패턴을 사용해 깔끔하게 분리해냈다. 독립된 JDBC 작업 흐름이 담긴 jdbcContextWithStatementStrategy()는 DAO메소드들이 공유할 수 있게 되었다.
DAO 메소드는 전략 패턴의 클라이언트로서 컨텍스트에 해당하는 jdbcContextWithStatementStrategy() 메소드에 적절한 전략, 즉 바뀌는 로직을 제공해주는 방법으로 사용할 수 있다. 여기서 컨텍스트는 PreparedStatement를 실행하는 JDBC의 작업 흐름이고, 전략은 PreparedStatement를 생성하는 것이다.
- 전략 클래스의 추가 정보
이번에 add() 메소드에 적용해보자. 변하는 부분을 AddStatement 클래스를 만들어 옮겨 담자.
add() 메소드는 delete()메소드와 달리 user라는 변수를 필요로 하고 있다. 따라서 AddStatement의 전략을 수행하기 위해 user를 제공해줘야 한다. user 정보를 생성자를 통해 전달해주도록 수정한다.
앞으로 비슷한 기능의 DAO 메소드가 필요할 때마다 이 Statement 전략과 jdbcContextWithStatementStategy() 컨텍스트를 활용할 수 있으니 코드를 만들다가 실수할 염려가 없어졌고, 코드도 간결해졌다.
- 전략과 클라이언트의 동거
코드가 간결해졌지만 좀 더 개선할 부분이 있다.
첫째로 DAO 메소드마다 새로운 StatementStragety 구현 클래스를 만들어야 한다는 점이다. 이렇게 되면 기존 UserDao때보다 클래스 파일의 개수가 많이 늘어난다.
둘째로 전달한 user와 같은 부가적인 정보가 있는 경우 이를 위해 오브젝트를 전달받는 생성자와 이를 저장해둘 인스턴스 변수를 번거롭게 만들어야 한다.
이 두 문제를 해결할 방법을 생각해보자.
로컬 클래스
클래스 파일이 많아지는 문제는 StatementStrategy 전략 클래스를 매번 독립된 파일로 만들지 말고 UserDao 클래스 안에 내부 클래스로 정의해버리는 것이다. 특정 메소드에서만 사용되는 것이라면 아래와 같이 로컬 클래스로 만들 수도 있다.
AddStatement 클래스를 로컬 클래스로서 add()메소드 안에 집어넣은 것이다. 이런 방식은 생소하지만 자바 언어에서 허용하는 클래스 선언 방법의 하나다. 이런 방식의 장점은 아래 그림처럼 생성자를 통해 번거롭게 UserVO 오브젝트를 전달해줄 필요 없이 바로 사용할 수 있다는 것이다.
익명 내부 클래스
AddStatement 클래스는 add() 메소드에서만 사용할 용도로 만들어졌다. 그렇다면 자바에서 이름조차 필요 없는 익명 내부 클래스를 이용해 좀 더 간결하게 클래스 이름도 제거할 수 있다.
- 클래스 분리
앞에서 만든 jdbcContextWithStatementStrategy()는 다른 DAO에서도 사용 가능하다.
그러니 jdbcContextWithStatementStrategy()를 UserDao 클래스 밖으로 독립시켜서 모든 DAO가 사용할 수 있게 해 보자.
분리해서 만든 클래스를 JdbcContext라고 만들고 옮길 메소드 이름을 workWithStatementStrategy()라는 이름으로 바꾸자.
그런데 메소드를 옮기면 DataSource가 필요한 것은 UserDao가 아니라 JdbcContext가 되어 버린다. DB커넥션을 필요로 하는 코드는 JdbcContext에 있기 때문이다.
그리고 분리된 JdbcContext 클래스를 UserDao가 DI 받아서 사용할 수 있게 만든다.
빈 의존관계 변경
새롭게 작성된 오브젝트 간의 의존관계를 살펴보고 이를 스프링 설정에 적용해보자.
UserDao는 JdbcContext에 의존하고 있다. 하지만 JdbcContext는 구체 클래스이다. 스프링의 DI는 기본적으로 인터페이스를 사이에 두고 의존 클래스를 바꿔서 사용하도록 하는 게 목적이다. 하지만 JdbcContext의 구현 방법이 바뀔 가능성이 없기 때문에 인터페이스를 사이에 두지 않고 DI를 적용하는 특별한 구조가 된다.
빈 의존관계를 따라서 XML 설정 파일을 수정하자. test-applicationContext.xml을 아래와 같이 수정하자.
이렇게 설정은 다 마쳤고 UserDaoTest를 실행해서 이상이 없는지 확인해보자.
- JdbcContext의 특별한 DI
스프링 빈으로 DI
이렇게 인터페이스를 사용하지 않고 DI를 적용하는 것은 문제가 있지 않을까?
물론 상관은 없다.
JdbcContext를 스프링을 이용해 UserDao 객체에서 사용하게 주입했다는 건 DI의 기본을 따르고 있다고 볼 수 있다.
JdbcContext를 UserDao와 DI구조를 만들어야 할 이유를 생각해보자.
첫째, JdbcContext가 스프링 컨테이너의 싱글톤 레지스트리에서 관리되는 싱글톤 빈이 되기 때문이다.
둘째, JdbcContext가 DI를 통해 다른 빈에 의존하고 있기 때문이다. 이 이유가 중요하다. JdbcContext는 dataSource 프로퍼티를 통해 DataSource 오브젝트를 주입받도록 되어 있다. DI를 위해서는 주입받는 오브젝트와 하는 오브젝트 둘 다 빈으로 등록이 되어야 한다. 스프링이 생성하고 관리하는 IoC 대상이어야 DI에 참여할 수 있기 때문이다.
단, 이런 클래스를 바로 사용하는 코드 구성을 DI에 적용하는 것은 가장 마지막 단계에서 고려해볼 사항임을 잊지 말자.
'Spring > 토비의 스프링 정리' 카테고리의 다른 글
5장 서비스 추상화 (0) | 2022.04.14 |
---|---|
3.5 템플릿과 콜백 (0) | 2022.04.09 |
3.1 다시 보는 초난감 DAO (0) | 2022.04.07 |
2.5 학습 테스트로 배우는 스프링 (0) | 2022.04.06 |
2.4 스프링 테스트 적용 (0) | 2022.04.05 |