티스토리 뷰
JDK 1.7 버전 부터 추가된 try-with-resources.
이런게 있다 정도만 알았고, 언제 사용하는지 정확히 몰랐다.
1. try-with-resources가 생겨난 배경
public static void main(String[] args) {
DataInputStream dataInputStream = null;
FileInputStream fileInputStream;
try {
fileInputStream = new FileInputStream("score.dat");
dataInputStream = new DataInputStream(fileInputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
try {
if (dataInputStream != null) {
dataInputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
위 코드를 보자.
위 코드는 DataInputStream을 사용해 파일로부터 데이터를 읽는 코드이다.
데이터를 읽는 도중에 예외가 발생해도 DataInputStream이 닫히도록 finally 블럭 안에 close를 넣었는데, close()가 IOException이라는 Checked exception을 발생시키기 때문에 try catch 처리를 또 해줘야 한다.
코드가 복잡해진다.
이 경우, 다음과 같이 변환할 수 있다.
int score = 0;
int sum = 0;
try (FileInputStream fis = new FileInputStream("score.dat");
DataInputStream dis = new DataInputStream(fis)) {
while (true) {
score = dis.readInt();
System.out.println(score);
sum += score;
}
} catch (FileNotFoundException e) {
System.out.println("sum = " + sum);
} catch (IOException e) {
e.printStackTrace();
}
2. 그래서 try-with-resources란?
try 다음 괄호 안에 객체를 생성하는 문장을 넣으면, 이 객체는 따로 close()를 호출하지 않아도 try 블럭을 벗어나는 순간 자동으로 close()가 호출된다.
그 다음에 catch 블럭이나 finally 블럭이 수행되는데, 이렇게 try-with-resources를 통해서 자동으로 객체의 close()가 호출 될 수 있으려면 클래스가 AutoCloseable 인터페이스를 구현한 것이어야만 한다.
public interface AutoCloseable {
/**
* Closes this resource, relinquishing any underlying resources.
* This method is invoked automatically on objects managed by the
* {@code try}-with-resources statement.
...
*/
void close() throws Exception;
}
그럼 어떻게 이게 가능한 것일까?
try 이후 괄호 안에 있는 코드를 인식해 close가 필요한 코드가 있으면, 바이트코드를 조작해 close를 생성하는 것이다.
※ try 이후 괄호 안에 여러개의 객체가 있다면, close()가 호출 되는 순서는 reverse이다.
https://stackoverflow.com/questions/52699539/try-with-resources-close-order
3. 적용 후기
다음은 스프링 DB 접근기술 1편의 JDBC를 이용한 데이터베이스 CRUD 예제이다.
package hello.jdbc.repository;
import hello.jdbc.connection.DBConnectionUtil;
import hello.jdbc.domain.Member;
import lombok.extern.slf4j.Slf4j;
import java.sql.*;
import java.util.NoSuchElementException;
/**
* JDBC - DriverManager 사용
*/
@Slf4j
public class MemberRepositoryV0 {
// jdbc를 이용해 member 객체 저장
public Member save(Member member) throws SQLException {
String sql = "insert into member(member_id, money) values (?,?)";
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = getConnection();
pstmt = conn.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(conn, pstmt, null);
}
}
public Member findById(String memberId) throws SQLException {
String sql = "select * from member where member_id = ?";
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = getConnection();
pstmt = conn.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(conn, pstmt, rs);
}
}
public void update(String memberId, int money) throws SQLException {
String sql = "update member set money=? where member_id=?";
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, money);
pstmt.setString(2, memberId);
int resultSize = pstmt.executeUpdate();
log.info("resultSize={}", resultSize);
} catch (SQLException e) {
log.error("db error", e);
throw e;
} finally {
close(conn, pstmt, null);
}
}
public void delete(String memberId) throws SQLException {
String sql = "delete from member where member_id = ?";
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, memberId);
int resultSize = pstmt.executeUpdate();
log.info("resultSize={}", resultSize);
} catch (SQLException e) {
log.error("db error", e);
throw e;
} finally {
close(conn, pstmt, null);
}
}
private void close(Connection conn, Statement stmt, ResultSet resultSet) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
log.info("error", e);
}
}
if (stmt != null) {
try {
stmt.close(); //SQLException 터져도 잡음.
} catch (SQLException e) {
log.info("error", e);
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
log.info("error", e);
}
}
}
private static Connection getConnection() {
return DBConnectionUtil.getConnection();
}
}
위 코드의 close() 메서드에 주목하자.
또한, Connection, Statement, ResultSet은 모두 AutoCloseable 인터페이스를 구현하고 있다.
위 코드를 아래 코드와 같이 Refactoring 해 보았다.
package hello.jdbc.repository;
import hello.jdbc.connection.DBConnectionUtil;
import hello.jdbc.domain.Member;
import lombok.extern.slf4j.Slf4j;
import java.sql.*;
import java.util.NoSuchElementException;
/**
* JDBC - DriverManager 사용
*/
@Slf4j
public class MemberRepositoryV0 {
// jdbc를 이용해 member 객체 저장
public Member save(Member member) throws SQLException {
String sql = "insert into member(member_id, money) values (?,?)";
try (Connection conn = getConnection();
PreparedStatement pstmt = conn.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;
} catch (NullPointerException e) {
log.info("NPE 발생", e);
}
return member;
}
public Member findById(String memberId) throws SQLException {
String sql = "select * from member where member_id = ?";
try (Connection conn = getConnection();
PreparedStatement pstmt = createPreparedStatement(sql, conn, memberId);
ResultSet 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;
} catch (NullPointerException e) {
log.info("NPE 발생", e);
}
return new Member();
}
private PreparedStatement createPreparedStatement(String sql, Connection conn, String memberId) throws SQLException {
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, memberId);
return pstmt;
}
public void update(String memberId, int money) throws SQLException {
String sql = "update member set money=? where member_id=?";
try (Connection conn = getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, money);
pstmt.setString(2, memberId);
int resultSize = pstmt.executeUpdate();
log.info("resultSize={}", resultSize);
} catch (SQLException e) {
log.error("db error", e);
throw e;
} catch (NullPointerException e) {
log.info("NPE 발생", e);
}
}
public void delete(String memberId) throws SQLException {
String sql = "delete from member where member_id = ?";
try (Connection connection = getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
preparedStatement.setString(1, memberId);
int resultSize = preparedStatement.executeUpdate();
log.info("resultSize={}", resultSize);
} catch (SQLException e) {
log.error("db error", e);
throw e;
} catch (NullPointerException e) {
log.info("NPE 발생", e);
}
}
private static Connection getConnection() {
return DBConnectionUtil.getConnection();
}
}
테스트도 잘 돌아갔다.
이전 코드에서는 close시 예외 발생 경우를 모두 try catch로 처리해 줬어야 했다.
하지만 try-with-resources 사용을 통해서 close() 메서드를 삭제시킬 수 있었다.
4. Java 9 이상에서 향상된 try-catch-resources 사용방법
Java7에서 try-with-resources를 사용할때 close() 메서드 자동 호출을 위해서 꼭 try 블록의 소괄호 안에서 자원 할당을 해야만 했었다.
하지만, Java 9 부터는 try 블록 밖에서 선언된 객체를 참조 할 수 있다.
public class ClassEx {
public methodEx() throws Exception {
Connection conn = DriverManager.getConnection("...");
Statement stat = conn.createStatement();
ResultSet rs = stat.executeQuery("SELECT 1 from dual")
try (conn; stat; rs) {
// ...
} catch (Exception e) {
// ...
}
}
}
[Reference]
https://ryan-han.com/post/java/try_with_resources/
https://catch-me-java.tistory.com/46
https://stackoverflow.com/questions/8066501/how-should-i-use-try-with-resources-with-jdbc
https://hyoj.github.io/blog/java/basic/jdbc/jdbc-try-catch-tip.html
'Programming > Java' 카테고리의 다른 글
📝 Function.identity() vs i -> i, 람다식에서 무엇을 사용해야 할까? (0) | 2024.01.30 |
---|---|
Scanner와 BufferedReader / BufferedWriter의 차이점과 사용법 (0) | 2023.06.05 |
Java Exception: Checked vs Unchecked, @SneakyThrows (0) | 2023.02.28 |
@SafeVarargs Annotation (추가 학습 필요) (0) | 2022.12.19 |
Trouble Shooting: java list ( Arrays.asList(), List.of() )사용시 UnsupportedOperationException 해결법 (0) | 2022.12.19 |
- Total
- Today
- Yesterday