Spring JDBC로 알아보는 예외 덩어리 처리 방법
[ 문제 상황 ]
JDBC관련 강의를 공부하다가 JDBC로 쿼리를 작성할 때 매 줄마다 예외가 터지는 모습을 보았다. 이런 상황일 때 어떻게 처리해야 하는지를 기록해두고 싶었고 한번 상황을 봐보자.
JDBC로 DB와 연결을 해서 데이터를 주고 받으려면 총 아래의 3단계의 과정을 순서대로 거쳐야 한다.
1. DB 커넥션
2. SQL 전달
3. 결과 응답
이 각각의 상황마다 SQLException이 터진다. 이걸 처리하려면 순서대로 객체를 획득하는 순서에 따라 예외를 잡게 하고 반대 순서대로 객체 예외를 처리해주어야 한다. 예를 들어 SQL 전달 객체까지 획득했다면 SQL 전달 객체 처리 -> DB 커넥션 객체 처리 순서대로 해주어야 한다. 이 과정을 한번 봐보자.
[ 해결 과정 ]
우선 아래의 상황은 JDBC를 이용해서 getConnection()에서 DB와의 연결을 얻고 con.prepareStatement(sql)로 sql을 전달한 뒤 응답을 받는 과정이다. 이 때 Connection이 실패하면 getConnection에서는 예외를 던진다. 그리고 prepareStatement를 실패해도 예외를 던진다. Result를 제대로 받지 않아와도 예외를 던진다.
이 코드에서 조심해야 할 점은 connection을 얻고 prepareStatment를 실패해서 예외가 던져졌을 때 con.close()를 제대로 호출해주지 않으면 con은 계속 유지된 채로 방치된다는 것이다. 이런 현상이 지속되면 나중엔 커넥션이 필요할 때 제대로 동작하지 않을 가능성이 있다. Result를 받았을 때도 아래의 두 개가 제대로 반환되지 못한 채로 쌓인다. 이 때 어떻게 처리해야 할까?
public Member findById(String memberId) throws SQLException {
String sql = "select * from member where member_id = ?";
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try{
con = getConnection();
pstmt = con.prepareStatement(sql);
pstmt.setString(1, memberId);
rs = pstmt.executeQuery();
if(rs.next()){
Member member = new Member();
member.setMemberId(rs.getString("member_id"));
member.setMoney(rs.getInt("money"));
return member;
} else{
throw new NoSuchElementException("member not found memberId =" + memberId);
}
} catch(SQLException e){
log.error("db error", e);
throw e;
} finally{
close(con, pstmt, rs);
}
}
그 방법은 finally 부분에 있다. 예외가 딱 던져졌을 때 catch 문에서 SQLException이 발생했을 때 예외를 던지고 행동하는 부분이 finally 부분이다. 앞에서는 커넥션을 얻고 SQL을 전달하고 결과를 얻은 것과 반대 순서대로 close()를 try 문을 통해 아래처럼 다 처리해주어야 한다. 그리고 close도 Exception을 뱉을 수 있는 상황이니 그 부분에 대한 처리도 각각 try-catch문으로 해줘야 한다. 안해주면 저기서 종료돼버리기 때문에 아직 close 못한 게 그대로 남아있게 된다.
private void close(Connection con, Statement stmt, ResultSet rs){
if(rs != null){
try{
rs.close();
} catch (SQLException e){
log.info("error", e);
}
}
if(stmt != null){
try{
stmt.close(); // Exception
} catch(SQLException e){
log.info("error", e);
}
}
if(con != null){
try{
con.close(); // Exception
} catch(SQLException e){
log.info("error", e);
}
}
}
사실상 맨 위의 findById까지도 예외를 던지는 상황이니, Service나 Controller 단에서도 저 예외를 또 처리해야줘야 할 것이다. 아무튼 JDBC는 Exception 지옥이다. 저렇게 개발을 안해도 돼서 너무 좋다.
[ 참고 자료 ]
스프링 DB 1편 - 김영한 강의