[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 하여 사용하셔야 합니다.