이번 포스트에서는 try~with~resource에서 resource의 close 시점에 대해 살펴보자.
try~with~resource
resource의 자동 close
try~with~resource 문장은 AutoCloseable 한 resource를 대상으로 사용이 끝나면 자동으로 close()를 호출해주는 기능을 가지고 있다.
// before try~with~resource
@Override
public int insert(Connection con, SsafyMember dto) throws SQLException {
int result = -1;
StringBuilder sql = new StringBuilder("sql 작성");
PreparedStatement pstmt = con.prepareStatement(sql.toString()); // resource
pstmt.setString(1, dto.getUserId());
try {
// business logic 처리
}finally{
if(pstmt!=null){ // 자원의 명시적 반납 처리
pstmt.close();
}
}
return result;
}
// after try~with~resource
@Override
public int insert(Connection con, SsafyMember dto) throws SQLException {
int result = -1;
StringBuilder sql = new StringBuilder("sql 작성");
PreparedStatement pstmt = con.prepareStatement(sql.toString()); // resource
pstmt.setString(1, dto.getUserId());
try (pstmt) { // pstmt 자동 close 처리
// business logic 처리
}
return result;
}
자동 close()의 호출 시점?
그런데 코딩 하는 과정에 재밋는 현상을 발견했다.
@Override
public int update(SsafyMember member) throws SQLException {
int result = 0;
Connection con = DBUtil.getUtil().getConnection();
try(con) {
int i = 1/0; // --> 예외 전파됨
con.commit();
}catch(Exception e) {
con.rollback();
throw e;
}
return result;
}
위와 같이 코드를 작성한 상태에서 만약 [문제 발생]부분에서 예외가 발생하면 코드는 어떻게 흘러갈까? 당연히 예외가 catch되서 Transaction이 rollback될 것 같았는데 아래와 같은 오류 메시지를 만나게 되었다.
java.sql.SQLNonTransientConnectionException: No operations allowed after connection closed.
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:111)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:98)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:90)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:64)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:74)
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:73)
at com.mysql.cj.jdbc.ConnectionImpl.rollback(ConnectionImpl.java:1823)
at com.ssafy.member.model.service.SsafyMemberServiceImpl.insert(SsafyMemberServiceImpl.java:32)
at com.ssafy.member.test.DaoTest.insertTest(DaoTest.java:33)
at com.ssafy.member.test.DaoTest.main(DaoTest.java:12)
SsafyMemberService.java의 32라인은 con.rollback()이 실행되는 부분이다. close 한 적이 없는데 어떻게 된 일이지? catch 되기 전에 close()가 이미 진행되었다는 말인가?
try~with~resource의 동작
사실 우리가 편하다고 생각하는 자동화 되는 코드들은 내가 써야할 코드들을 컴파일러가 대신 써주는것이 대부분이다. try~with~resource도 마찬가지이다. 우리가 작성한 코드들은 내부적으로 변경된다.( Chapter 14. Blocks and Statements (oracle.com))
try ResourceSpecification
Block
Catches
Finally
try {
try ResourceSpecification
Block <- 이미 여기서 리소스는 close 됨
}
Catches <- 이 시점에 resource에 접근하면?
Finally
좌측의 코드는 try~with~resource를 이용해서 작성된 코드다. 이 코드는 실제로 우측의 코드처럼 변경된다. 즉 우리가 만들었던 try~with~resource는 거대한 try 문장으로 감싸지는 것이고 이 내부에서 close()가 이뤄진다. 따라서 Catches에 도달했을 때는 이미 resource는 close된 이후가 된다.
따라서 transaction 처리에서 처럼 resource가지고 commit/rollback등의 작업을 명시적으로 해야하는 경우에는 try~with~resource 문장을 사용하는 것은 적절하지 않다.
그렇지 않고 그냥 종료하면 되는 경우(stream을 닫는다든가.., PreparedStatement, ResultSet 등도)는 완전 좋다. ㅎ