티스토리 뷰

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

 

Try-with-resources close order

I am looking at examples of try-with-resources in Java and I understand the following one: try (Connection conn = DriverManager.getConnection(url, user, pwd); Statement stmt = conn.createStat...

stackoverflow.com

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

최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday