Java로 백엔드나 시스템 프로그래밍을 하다 보면, 파일, DB 커넥션, 네트워크 소켓 등 다양한 자원을 사용하게 됩니다. 이런 자원들은 사용 후 반드시 close()를 호출해서 반환해야 메모리 누수, 커넥션 풀 고갈 같은 심각한 문제를 방지할 수 있습니다.
Java 7 이전 방식: try-finally
Connection conn = null;
try {
conn = dataSource.getConnection();
// 쿼리 실행
conn.close(); // 단순히 close() 호출
} catch (SQLException e) {
e.printStackTrace();
}
⚠️ 이 방식의 단점
- getConnection()에서 예외가 발생하면 conn은 null이고 close()도 호출되지 않음
- 쿼리 실행 중 예외가 발생하면 close()까지 도달하지 못하고 커넥션이 닫히지 않음
- 자원 누수 발생 가능성 → 커넥션 풀 고갈, DB 과부하
try-finally방식
Connection conn = null;
try {
conn = dataSource.getConnection();
// 쿼리 실행
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}
✅ 단순히 close()만 썼을 때보다 나은 점
- 예외가 발생해도 finally 블록은 무조건 실행되므로 자원 누수를 방지할 수 있습니다.
- try-catch만 사용할 경우 close()가 호출되지 않는 위험이 있지만, try-finally는 이를 보완해줍니다.
❌ 하지만 문제는?
- 코드가 길고 중복이 많으며
- 리소스가 많아질수록 예외 처리 블록이 복잡해지고 실수할 가능성이 높습니다.
Java 7 이후 방식: try-with-resources
Java 6 이하에서는 try-finally 문을 이용해 자원을 수동으로 닫아야 했습니다. Java 7 부터 try-with-resources를 사용할 수 있습니다. try-with-resources는 AutoCloseable 인터페이스를 구현한 객체를 try() 안에서 선언하면, try 블록이 끝날 때 자동으로 해당 자원의 close() 메서드를 호출해 주는 문법입니다. 이를 통해 개발자가 직접 finally에서 close()를 호출하는 번거로움을 줄이고, 자원 누수 위험도 줄일 수 있습니다.
✅ 장점
- finally 없이도 자원이 안전하게 닫힘
- 코드가 훨씬 간결하고 가독성이 좋음
- 여러 자원(ResultSet, Statement, Connection)도 순서대로 자동 close
극단적으로 아래와 같은 코드가 있다고 가정했을 때, 중복 코드가 많고, 하나라도 close()를 빼먹으면 커넥션 누수가 발생하고, 예외가 중첩되면 디버깅이 어렵습니다.
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
pstmt = conn.prepareStatement("SELECT * FROM users");
rs = pstmt.executeQuery();
while (rs.next()) {
System.out.println(rs.getString("username"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (rs != null) {
try { rs.close(); } catch (SQLException e) {}
}
if (pstmt != null) {
try { pstmt.close(); } catch (SQLException e) {}
}
if (conn != null) {
try { conn.close(); } catch (SQLException e) {}
}
}
하지만 try-with-resources로 코드를 작성한다면 이런식으로 개선할 수 있습니다.
try (
Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM users");
ResultSet rs = pstmt.executeQuery()
) {
while (rs.next()) {
System.out.println(rs.getString("username"));
}
} catch (SQLException e) {
e.printStackTrace();
}
🤔 그렇다면 AutoCloseable 또는 Closeable을 직접 구현해야 하나?
✅ 일반적으로는 그럴 필요 없습니다.
Java의 대부분의 리소스 클래스는 이미 AutoCloseable 또는 Closeable을 구현하고 있기 때문입니다.
- Connection, PreparedStatement, ResultSet
- BufferedReader, FileInputStream, Scanner
- 이들은 모두 이미 구현 완료된 상태이므로 바로 try-with-resources에서 사용할 수 있습니다.
내가 만든 자원 클래스를 자동으로 닫고 싶다면?
그럴 경우에는 AutoCloseable 인터페이스를 직접 구현해야 합니다.
public class MyResource implements AutoCloseable {
public void doSomething() {
System.out.println("작업 중...");
}
@Override
public void close() {
System.out.println("리소스 닫기 완료!");
}
}
try (MyResource res = new MyResource()) {
res.doSomething();
}
// → 자동으로 res.close() 호출
'cs > Java' 카테고리의 다른 글
[Java] System.in은 한 번만 선언해야 할까? (0) | 2025.04.29 |
---|