[Java] try-with-resources (JDK 7 ⬆)

JDK 7에 추가된 try-with-resources에 대해 소개합니다.

일반적으로 JDBC 연동을 위해 Connection, PreparedStatement, ResultSet를 try 바깥에 일단 null로 생성하고 다 사용한 뒤에는 finally를 통해 전체를 close()를 하는 식으로 이용하는데요. 최근에 JSP와 Servlet를 사용하여 JDBC와 연동하여 간단한 WAS를 작성하여 코드 리뷰를 받아보았습니다. 코드 리뷰해주시는 분이 JDK 7에서부터 추가된 'try-with-resources'를 소개해주셨습니다. 그래서 전에 작성한 JDBC 예제에도 반영하였는데요. 이번 글에서는 간단하게 try-with-resources를 알아보겠습니다.


기존에는?

public List<Person> searchPerson(String keyword) {
        List<Person> people = new ArrayList<>();
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;

        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection(dbUrl, dbUser, dbPasswd);
            String selectQuery = "SELECT name, age, job, phone FROM personlist WHERE name LIKE ?";
            ps = conn.prepareStatement(selectQuery);
            ps.setString(1, keyword);
            rs = ps.executeQuery();

            while(rs.next()) {
                String name = rs.getString("name");
                int age = rs.getInt("age");
                String job = rs.getString("job");
                String phone = rs.getString("phone");

                people.add(new Person(name, age, job, phone));
            }

        } catch(Exception e) {
            e.printStackTrace();
        } finally {
            if(rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

            if(ps != null) {
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

            if(conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        return people;
    }

기존에는 finally 부분에서 close() 작업을 해주었습니다. 이 작업을 해주기 위해서 try-catch-finally 바깥에 Connection, PreparedStatement, ResultSet을 일단, null로 정의해주고 try에서 Connection을 할당하고, 쿼리를 구성하고, 결과를 받아오고 했는데요.

close() 부분 때문에 불필요하게 똑같은 소스가 반복되는 경우가 많습니다. 또한 null 일 때 close()하지 않도록 설정하기 위해 if 문도 계속 들어가있습니다. 이러한 부분 때문에 가독성을 상당히 떨어트리고, 불필요한 소스 코드를 적게 됩니다.


try-with-resources를 이용한 간단한 구현

하지만, try-with-resources를 이용해 구현을 하면 상당히 간단해집니다.

try (
        Connection conn = DBUtil.getConnection();
        PreparedStatement ps = conn.prepareStatement("SELECT * FROM personlist");
        ResultSet rs = ps.executeQuery() // 마지막 줄에는 ;를 붙이지 않아도 됨.
    ) {
        // 결과 레코드가 끝날때까지 반복
        while(rs.next()) {
            String name = rs.getString("name");
            int age = rs.getInt("age");
            String job = rs.getString("job");
            String phone = rs.getString("phone");

            people.add(new Person(name, age, job, phone));
        }

        } catch(Exception e) {
            e.printStackTrace();
        }

        return people;
    }

try 시작할 때, () 안에 close()를 해야할 리소스를 정의합니다. null로 정의하지 않는 이유는 () 안에 들어가는 객체들은 final 형태이기에 재할당이 불가능합니다. 즉, 한 번만 사용할 수 있는 것입니다.

코드가 상당히 깔끔해졌음을 확인할 수 있습니다.


PreparedStatement에 파라미터는 어떻게 넣고, 실행하나요?

많은 경우에는 PreparedStatement에 ?를 이용해 동적인 파라미터 값을 넣을겁니다. 그런데, 아래와 같이 하면 에러가 납니다.

try (
        Connection conn = DBUtil.getConnection();
        PreparedStatement ps = conn.prepareStatement("SELECT * FROM personlist WHERE name = ?");
        ps.setString(1, "홍길동"); // ERROR!
        ResultSet rs = ps.executeQuery()
    ) {
        // 결과 레코드가 끝날때까지 반복
        while(rs.next()) {
            String name = rs.getString("name");
            int age = rs.getInt("age");
            String job = rs.getString("job");
            String phone = rs.getString("phone");

            people.add(new Person(name, age, job, phone));
        }

        } catch(Exception e) {
            e.printStackTrace();
        }

        return people;
    }

리소스를 정의하는 괄호 안에서는 setString 같은 메소드를 사용할 수 없는데요. 그러면 어떻게 파라미터를 정의하고, ResultSet에 데이터를 받아올까요. 정답은 try-with-resources를 중첩하는 것입니다.

try (
        Connection conn = DBUtil.getConnection();
        PreparedStatement ps = conn.prepareStatement("SELECT * FROM personlist WHERE name = ?")
    ) {
        ps.setString(1, "홍길동");

        try (ResultSet rs = ps.executeQuery()) {
            // 결과 레코드가 끝날때까지 반복
            while(rs.next()) {
                String name = rs.getString("name");
                int age = rs.getInt("age");
                String job = rs.getString("job");
                String phone = rs.getString("phone");

                people.add(new Person(name, age, job, phone));
            }
        } catch(Exception e) {
            e.printStackTrace();
        }
        
    } catch(Exception e) {
        e.printStackTrace();
    }

        return people;
}

위와 같이 중첩해서 사용하면 됩니다.


try-with-resources에 사용할 수 있는 객체들은?

try-with-resources에 객체를 넣는다고 모두 close()가 실행되는 것은 아닙니다. close()가 자동으로 실행되는 객체들은 AutoCloseable를 구현한 객체입니다. 만일, 자신의 클래스가 자동으로 close() 되는 것을 원하신다면, AutoCloseable를 implements 하여 사용하셔야 합니다.