Search
Duplicate

3.1 다시 보는 초난감 DAO

태그

3.1.1 예외처리 기능을 갖춘 DAO

DB커넥션이라는 제한적인 리소스를 공유해 사용하는 서버에서 동작하는 JDBC 코드에는 반드시 예외처리를 해주어야 한다. 정상적인 JDBC 코드의 흐름을 따르지 않고 중간에 어떤 이유로든 예외가 발생했을 경우에도 사용한 리소스를 반드시 반환하도록 만들어야 하기 때문이다.

JDBC 수정 기능의 예외처리 코드

문제코드
public void deleteAll() throws SQLException{ Connection c = dataSource.getConnection(); // 여기서 예외가 발생하면 바로 메서드 실행이 중단됨 PreparedStatement ps = c.prepareStatement("delete from users"); ps.executeUpdate(); // ps.close(); c.close(); }
Java
복사
이 코드에서 PreparedStatemment를 처리하는 중에 예외가 발생하면 메서드 실행을 끝마치지 못하고 바로 메서드를 빠져나가게 된다. 이때 문제는 Connection과 PreparedStatement의 close() 메서드가 실행되지 않아서 제대로 리소스가 반환되지 않을 수 있다는 점이다.
일반적으로 서버에서는 제한된 개수의 DB 커넥션을 만들어서 재사용 가능한 풀로 관리한다(Connection Pool). DB 풀은 매번 getConnection() 으로 가져간 커넥션을 명시적으로 close() 해서 돌려줘야지만 다시 풀에 넣었다가 다음 커넥션 요청이 있을 때 재사용할 수 있다. 그런데 이런 식으로 오류가 날 때마다 반환되지 못한 Connection이 계속 쌓이면 어느 순간 커넥션 풀에 여유가 없어지고 리소스가 모자란다는 심각한 오류를 내며 서버가 중단될 수 있다.
그래서 이런 JDBC 코드에서는 어떤 상황에서도 가져온 리소스를 반환하도록 try ~ catch ~finally 구문의 사용을 권장하고 있다.
수정된 코드
public void deleteAll() throws SQLException{ Connection c = null; PreparedStatement ps = null; try{ // 예외가 발생할 수 있는 코드 부분을 모두 try 블록으로 묶어줌 c = dataSource.getConnection(); ps = c.prepareStatement("delete from users"); ps.executeUpdate(); } catch (SQLException e){ throw e; } finally { // finally 블록은 예외의 발생 여부와 관계없이 항상 실행된다. if(ps != null){ try{ ps.close(); } catch (SQLException e){ // ps.close 메서드 자체에서도 예외가 발생할 수 있다. 이부분을 처리해줌 } if(c != null){ try{ c.close(); } catch(SQLException e){ // ps.close와 마찬가지로 c.close 메서드 자체의 예외를 잡아준다. } } } }
Java
복사
이때 주의해야 하는 부분이 2가지가 있다.
(1) 예외가 어느 시점에 발생하는가에 따라 어떤 것의 close() 메서드를 호출하는지가 달라진다.
만약 getConnection()에서 DB 커넥션을 가져오다가 예외가 발생하는 경우 ps와 c 모두 null 상태이다. null 상태의 변수에 close() 메서드를 호출하게 되면 NullPointerException 이 발생하게 되므로 이를 위해서 if(ps != null) 과 같이 예외처리를 해주어야 한다.
(2) close() 메서드 자체도 예외를 발생시킬 수 있는 메서드이다.
이미 deleteAll에 SQLException이 던져진다고 선언되어 있으니 close() 에는 try ~ catch가 없어도 되지 않을 까 생각할 수 있다. 하지만 try ~ catch 블록 없이 ps.close() 를 처리하다가 예외가 발생하면 아래의 c.close() 부분이 실행되지 않고 메서드를 빠져나가는 문제가 발생할 수 있다.

JDBC 조회 기능의 예외처리 코드

조회를 위한 JDBC 코드는 Connection, PreparedStatement 외에도 ResultSet을 추가적으로 더 고려해 주어야 한다.
public int getCount() throws SQLException{ Connection c = null; PreparedStatement ps = null; ResultSet rs = null; try{ c = dataSource.getConnection(); ps = c.prepareStatement("select count(*) from users"); // ResultSet도 다양한 SQLException이 발생할 수 있는 코드이므로 try 블록 안에 있어야 함 rs = ps.executeQuery(); rs.next(); return rs.getInt(1); } catch (SQLException e){ throw e; } finally { // finally 블록은 예외의 발생 여부와 관계없이 항상 실행된다. if(rs != null){ try{ rs.close(); } catch (SQLException e){ } if(ps != null){ try{ ps.close(); } catch (SQLException e){ } if(c != null){ try{ c.close(); } catch(SQLException e){ } } } }
Java
복사