Transaction을 JDBC로 작성할 때 항상 try-catch-finally을 사용해서 반납해야 하는 자원들을 close() 메소드를 호출하여 반납했다.
public Member save(Member member) throws SQLException {
String sql = "insert into member(member_id, money) values (?,?)";
Connection con = null;
PreparedStatement pstmt = null;
try{
con = getConnection();
pstmt = con.prepareStatement(sql);
pstmt.setString(1, member.getMemberId());
pstmt.setInt(2, member.getMoney());
pstmt.executeUpdate();
return member;
}catch(SQLException e){
log.error("db error",e);
throw e;
}finally {
close(con,pstmt, null);
}
}
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();
}catch(SQLException e){
log.info("error",e);
}
}
if(con != null){
try{
con.close();
}catch(SQLException e){
log.info("error", e);
}
}
}
물론 지금은 @Transactional 어노테이션이 이런 중복되는 코드들을 대신해준다. 하지만 File관련된 것들을 사용하려면 여전히 위와 같은 코드를 작성해야 하고 그럼 자원 반납 때문에 코드가 복잡해지고 실수나 에러로 자원을 반납하지 못하는 경우가 발생할 수 있다. 그리고 에러 스택 트레이스가 누락될 수 있는데 try-with-resources는 누락 없이 보여준다고 한다.
그래서 이런 문제를 해결하기 위해 try-with-resources라는 문법이 Java7부터 추가됐다. 근데 난 왜 몰랐지? 내가 배울 땐 다들 try-catch-finally를 써서 아무렇지 않게 사용했던 것 같다.
try-with-resources (Java 7)
이 문법은 자동으로 자원을 반납해준다.
무조건 반납해 주는 것은 아니고 AutoCloseable 인터페이스를 구현하고 있는 자원만 try-with-resources를 사용할 수 있다.
AutoCloseable은 기존 Closeable의 부모 인터페이스로 추가된 인터페이스이다. 신기한 게 Closeable을 상속받아 만든 것이 아닌 반대로 상위 클래스를 만들어서 Closeable이 상속받도록 만들었다. 그래서 기존 Closeable을 구현한 구현체들은 별다른 수정 없이 AutoCloseable을 구현하고 있는 형태가 되었다.
사용법은 아래와 같다
try(Connection con = getConnection();
PreparedStatement pstmt = con.prepareStatement(sql);){
pstmt.setString(1, member.getMemberId());
pstmt.setInt(2, member.getMoney());
pstmt.executeUpdate();
return member;
}catch(SQLException e){
log.error("db error",e);
throw e;
}
try의 소괄호에 close()가 필요한 자원들을 선언하고 내부에서 사용한다. 별도의 close() 메서드 호출이 없어도 자동으로 자원을 반납한다.
난 이때 정말 반납하는 게 맞나?라는 의문이 생겼다.
기존 try-catch-finally는 코드가 눈으로 보이니까 당연히 반납한다고 생각했지만 코드가 눈에 보이지 않으니 반납이 안되면 어쩌지?라는 생각이 들었다. 그래서 디버그 모드를 사용해서 확인해보았다.
정말 자원 반납이 될까?
try(Connection con = getConnection();
PreparedStatement pstmt = con.prepareStatement(sql);
BufferedReader br = new BufferedReader(new FileReader(new File("/Users/seungh/Desktop/personal-project/algorithm/codetree/CT_포탑부수기.java")))){
pstmt.setString(1, member.getMemberId());
pstmt.setInt(2, member.getMoney());
pstmt.executeUpdate();
}catch(SQLException e){
log.error("db error",e);
throw e;
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
int test = 0;
코드가 지저분하긴 하지만 기존의 커넥션 관련 자원과 혹시 몰라 파일 관련된 자원을 함께 try-with-resources에서 사용해 보았다. 코드엔 close() 메소드를 호출하지 않고 int test = 0를 선언해서 해당 부분에 브레이크 포인트를 찍어서 정말 객체가 반납됐는지 확인해 보았다.
- 첫 메소드 호출 시 확인을 위한 BufferedReader 객체와 Connection 객체는 찾을 수 없다고 나온다.
- try-with-resources를 사용해서 객체를 생성하고 중간에 브레이크 포인트를 걸어서 확인해 보니 br과 con이 생성된 것을 확인할 수 있었다. 그럼 예상대로라면 try를 빠져나온 후 br과 con이 자동으로 close()되어야 할 것이다
- br과 con을 모두 찾을 수 없는 것을 확인할 수 있다. try-with-resources는 AutoClosable 인터페이스를 구현하고 있으면 자동으로 close()를 호출해 주는 것을 확인할 수 있었다. try-catch-finally는 코드가 눈으로 보여서 의심이 안 됐는데 try-with-resources는 눈으로 볼 수 없어 직접 디버그 모드로 확인해 보니 자동으로 잘 닫히는 것을 보니 참 신기했다.
try-with-resources (Java 9)
참고로 Java 9부터는 아래와 같이 사용할 수도 있다.
Connection con = getConnection();
PreparedStatement pstmt = con.preparedStatement(sql);
try(con; pstmt;){
// 코드생략
}catch(SQLException e){
log.error("db error",e);
throw e;
}
정리
정리하자면 try-with-resources는 아래와 같은 이유로 사용해야 한다.
- try-catch-finally보다 간결한 코드를 만들 수 있다.
- 번거롭게 직접 자원 반납을 하지 않아도 된다.
- 실수나 에러로 인해 자원을 반납하지 못하는 상황을 미리 방지할 수 있다.
- 스택 트레이스가 누락되지 않는다.
'Java > Java' 카테고리의 다른 글
Java 양방향 Socket 통신시 발생한 에러(socket is closed, 무한 readLine) (0) | 2023.12.05 |
---|---|
자바 다시 학습 하면서 알게된 것(제네릭, Collection, Map) (0) | 2023.11.13 |
[Java] 가비지 컬렉션(Garbage Collection)과 5가지 알고리즘 (0) | 2023.04.23 |
[Java] 불변 객체와 final을 사용해야 하는 이유(feat.정적 팩토리 메소드) (0) | 2023.04.23 |
Java 정리(JVM, 객체지향, 싱글톤 패턴) (0) | 2023.01.09 |