학습 목표
스프링에 적용된 템플릿 기법을 살펴보고, 이를 적용해 완성도 있는 DAO코드를 만드는 방법을 알아보자.
UserDao코드에는 예외상황에 대한 처리라는 문제점이 남아있다.
먼저 UserDao의 가장 단순한 메소드인 deleteAll()을 살펴보자.
반환되지 못한 Connection이 계속 쌓이면 어느 순간에 커넥션 풀에 여유가 없어지고 리소스가 모자란다는 심각한 오류를 내며 서버가 중단될 수 있다. 장시간 운영되는 다중 사용자를 위한 서버에 적용하기에는 치명적인 위험을 내포하고 있다.
리소스 반환과 close()
close()메소드는 이름을 보면 열린 것은 닫는다는 의미지만 보통 리소스를 반환한다는 의미로 이해하는 것이 좋다. Connection과 PreparedStatament는 보통 풀 방식으로 운영된다. 미리 정해진 풀 안에 제한된 수의 리소스를 만들어두고 필요할 때 이를 할당하고, 반환하면 다시 풀에 넣는 방식으로 운영된다. 요청이 많은 서버에서는 미리 만들어둔 리소스를 돌려가면 사용하는 편이 훨씬 유리하다. 대신 사용한 리소는 빠르게 반환해야 한다. 그래서 close() 메소드는 리소스를 풀로 다시 돌려주는 역할을 한다.
그래서 이런 JDBC 코드에서는 어떤 상황에서도 가져온 리소스를 반환하도록 try/catch/finally 구문 사용을 권장하고 있다. deleteAll() 메소드에 이 구문을 적용해보자.
JDBC를 사용해본 사람이라면 익숙한 try/catch/finally 구문이다. DB 서버 문제로 커넥션을 가져오지 못하면 c는 null 상태고 이 상황에서 close()를 실행하면 NullPointerException이 발생한다. 그러므로 c와 pstmt가 null인지 아닌지 확인한 후에 close() 메소드를 실행하기 위해 if문 조건을 넣은 것이다.
JDBC 조회 기능의 예외처리
조회를 위한 JDBC 코드는 좀 더 복잡해진다. 바로 ResultSet도 반환해야 하기 때문이지만 ResultSet도 close() 메소드가 반드시 호출되도록 만들면 된다.
위 deleteAll()메소드와 별 차이는 없다. 모든 작업을 마쳤으니 Test를 통해 이상이 없는지 확인해보자.
3.2 변하는 것과 변하지 않는 것
- JDBC try/catch/finally 코드의 문제점
완성도가 높은 DAO코드가 됐지만 복잡한 try/catch/finally 블록이 2중으로 중첩까지 되어 나오는 데다, 모든 메소드마다 반복된다.
이런 코드를 효과적으로 다룰 수 있는 방법이 없을까?
개발자라면 당연히 가지는 의문이다. 1장에서 살펴봤던 것과 비슷한 문제이고 같은 방법으로 접근하면 되지만 조금 다르다.
분리와 재사용을 위한 디자인 패턴 적용
UserDao의 메소드를 개선하는 작업을 시작해보자. 먼저 성격이 다른 것을 찾아내자. deleteAll() 메소드를 보면 아래 그림과 같이 나뉜다.
로직에 따라서 변하는 부분을 변하지 않는 나머지 코드에서 분리하는 것이 어떨까?
메소드 추출
변하는 부분을 메소드로 추출한 후 deleteAll()의 모습은 아래와 같다.
메소드 추출로 분리를 했지만 분리되고 남은 메소드와 분리한 메소드가 뭔가 반대로 됐다. 이래선 분리한 의미가 없어진다.
템플릿 메소드 패턴의 적용
템플릿 메소드 패턴은 상속을 통해 기능을 확장해서 사용하는 부분이다. 변하지 않는 부분은 슈퍼 클래스에 두고 변하는 부분은 기능을 확장해서 사용하는 부분이다.
추출해서 메소드로 독립시킨 makeStatement() 메소드를 다음과 같이 추상 메소드 선언으로 변경한다. 이때 UserDao도 추상 클래스가 되어야 한다. 그리고 이를 상속하는 서브클래스를 만들어서 거기서 이 메소드를 구현한다.
이 방법의 가장 큰 단점은 DAO 로직마다 상속을 통해 새로운 클래스를 만들어야 한다는 점이다. UserDao의 JDBC 메소드가 4개라면 4개의 서브 클래스를 만들어야 한다. 이러면 장점보다 단점이 더 많다.
전략 패턴의 적용
템플릿 메소드 패턴보다 유연하고 확장성이 뛰어난 것이, 오브젝트를 아예 둘로 분리하고 클래스 레벨에서는 인터페이스를 통해서만 의존하도록 만드는 전략 패턴이다.
전략 패턴의 구조를 따라 PreparedStatement를 만들어주는 외부 기능을 인터페이스로 만들어두고 인터페이스의 메소드를 통해 PreparedStatement 생성 전략을 호출하면 된다. 이 내용을 인터페이스로 정의하면 아래와 같다.
이 인터페이스를 상속해서 실제 전략, 즉 바뀌는 부분인 PreparedStatement를 생성하는 클래스를 만들어보자.
이제 이것을 UserDao의 deleteAll()메소드에서 사용하면 전략 패턴을 적용했다고 볼 수 있다.
DI 적용을 위한 클라이언트/컨텍스트 분리
컨텍스트에 해당하는 부분은 별도의 메소드로 독립시켜보자.
이 메소드는 컨텍스트의 핵심적인 내용을 잘 담고 있다. 아래는 클라이언트 책임을 갖도록 재구성한 deleteAll()메소드이다.
이제 구조로 볼 때 완벽한 전략 패턴의 모습을 갖췄다. 특히 클라이언트가 컨텍스트가 사용할 전략을 정해서 전달한다는 면에서 DI 구조라고 이해할 수 있다. 이 구조가 기반이 돼서 앞으로 진행할 UserDao코드의 본격저인 개선 작업이 가능한 것이다.
'Spring > 토비의 스프링 정리' 카테고리의 다른 글
3.5 템플릿과 콜백 (0) | 2022.04.09 |
---|---|
3.3 JDBC 전략 패턴의 최적화, 3.4 컨텍스트와 DI (0) | 2022.04.08 |
2.5 학습 테스트로 배우는 스프링 (0) | 2022.04.06 |
2.4 스프링 테스트 적용 (0) | 2022.04.05 |
2.3 개발자를 위한 테스팅 프레임워크 JUnit (2) | 2022.04.04 |