UserDao 를 지금까지 리팩토링하였지만, 아직도 문제점이 많습니다.
먼저, UserDao의 findById 에서 ResultSet 객체도 close 해줘야하는데 하지 않았습니다.
다음과 같이 ResultSet 을 close 하게 수정해줍니다.
//UserDao.java
public User findById(String id) throws SQLException {
ResultSet rs = null;
try (Connection conn = this.dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement("select id, nick_name from User where id = ?")) {
ps.setString(1, id);
rs = ps.executeQuery();
if (!rs.next()) throw new EmptyResultDataAccessException(1);
return User.builder()
.id(rs.getString("id"))
.nickName(rs.getString("nick_name"))
.build();
} catch (Exception e) {
throw e;
} finally {
if (Objects.nonNull(rs)) {
try {
rs.close();
} catch (SQLException e) {
throw e;
}
}
}
}
일부러 해당 부분을 추가하지 않고 있었는데, 추가하니 소스가 지저분해졌습니다.
이제 다시 UserDao 의 메서드들을 살펴보면, 대부분 다음과 같은 구조로 되어있습니다.
//예외처리 (try-catch-finally)
//try-with-resource를 이용한 Connection, PreparedStatement get/close
try (Connection conn = this.dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement("SQL")) {
//실질적인 실행부 (쿼리 실행, 쿼리 파라미터 셋팅, ResultSet 처리)
} catch(Exception e) {
throw e;
} finally {
//남은 객체 close
}
try-catch-finally 문으로 예외처리하는 부분과 dataSource로부터 Connection 객체를 얻는 부분은 크게 변경될 일 없는 부분입니다.
하지만 지금은 UserDao 에 메서드를 추가할 때마다 위의 로직을 중복적으로 추가해야 하는 상황이고 심지어 다른 Dao 클래스에도 해당 로직을 똑같이 추가해야 합니다.
또한 서로 다른 Dao 사이라 하더라도 같은 SQL 명령어(insert, select, update, delete) 끼리는 실행부의 로직도 크게 다르지 않을 겁니다.
따라서, 위의 코드 중 변경이 거의 일어나지않는 부분을 따로 분리하여 리팩토링 해보겠습니다.
템플릿이란 코드 중 변경이 거의 일어나지 않으며 일정한 패턴으로 유지되는 부분을 변경이 자주 일어나는 부분으로부터 독립시켜서 효과적으로 활용할 수 있도록 하는 방법입니다.
위의 소스에서 예외처리 하는 부분과 dataSource로부터 Connection 객체를 얻는 등의 크게 변하지 않는 부분을 분리하여 재사용성을 높이는 방법이라고 할 수 있습니다.