🍔

Javaでtry-with-resourcesとPreparedStatementを組み合わせる時

2022/01/25に公開

1. 概要

Javaでデータベースの処理を書く時、Connectionなどは必ず閉じる必要がある。
なのでfinallyブロック内で閉じる処理を書くのだが、更にそのfinallyブロック内でnullチェックとか例外処理を書くことになりごちゃごちゃする。

で、Java7からはそれを簡単に書けるtry-with-resourcesという書き方があり、普通のStatementと組み合わせて使うときは特に疑問も無く書ける。

しかしPreparedStatementと組み合わせる時は「あれ、ResultSetがtry-with-resources構文の中に入らないけどええんか?ResultSet閉じなくてえんか?」と疑問に感じるところがあった。
調べてみると大丈夫らしいのでその内容をメモしておく。

2. tryとStatementの組み合わせ

try-with-resources + PreparedStatementの疑問に触れるにあたって、下記3つのパターン例を記載する。

  1. 普通のtryと普通のStatement
  2. try-with-resourcesと普通のStatement
  3. try-with-resourcesとPreparedStatement

2.1. 普通のtryと普通のStatement

まず普通のtryと普通のStatement。
Connectionとかの定義をtryの外に書いたり、finallyでnullとか例外処理とかしたりするのでごちゃごちゃしてくる。

//定義をtry内で書いちゃうとtry内で消えちゃうのでfinallyで処理できない。
//なので定義はtryの外側に書く。
Connection con = null;
Statement stmt = null;
ResultSet rs = null;

try{
    //実際にデータベース接続する処理とかはtry内で書く
    con = うんぬん;
    stmt = con.createStatement();

    String sql = "select * from hoge";
    rs = stmt.executeQuery(sql);

    その他なんかの処理

} catch (なんかの例外) {
    例外の処理
} finally {
    //finallyの中でrs、stmt、conをcloseする。
    //nullチェックとか例外処理もあってめんどくさい。
    if(rs != null){
        try{
            rs.close();
        } catch (SQLException e) {
            例外処理
        }
    }
        if(stmt != null){
        try{
            stmt.close();
        } catch (SQLException e) {
            例外処理
        }
    }
        if(con != null){
        try{
            con.close();
        } catch (SQLException e) {
            例外処理
        }
    }
}

2.2. try-with-resourcesと普通のStatement

try-with-resourcesの構文で書くと、かなりすっきりする。

//try-with-resources構文(tryの直後の括弧の中)でexecuteQueryをするので、
//SQLをその前に書いておかないといけない
String sql = "select * from hoge";

try(
    //この中にcloseすべきものを書ける
    Connection con = うんぬん;
    Statement stmt = con.createStatement();
    ResultSet rs = stmt.executeQuery(sql);
) {

    なんかの処理。ResultSetの中身取り出すとか。

} catch (なんかの例外) {
    例外の処理
} 

    なんということでしょう…ここにfinallyを書かなくてもいい。

2.3. try-with-resourcesとPreparedStatement

更にPreparedStatementを使うとき。
これで動くんだけど、ResultSet閉じられるのか?が気になる。

//PreparedStatement用に、「?」の入ったSQLを定義しておく。
String sql = "select * from ?";

try(
    //この中にcloseすべきものを書ける
    Connection con = うんぬん;
    PreparedStatement pstmt = con.prepareStatement(sql);
    //あれ、まだSQLの「?」の値セットしてないからここじゃクエリ実行できないね。
    //ResultSet rs = pstmt.executeQuery();
) {
    //ここで値セット。本当は画面から入力受けたりするけどひとまず直接値入れる。
    pstmt.setString(1,"hoge");

    //ここでクエリ実行。tryの後ろの括弧の中にResultSetの定義入れてないけど、いいのかしら…?
    ResultSet rs = pstmt.executeQuery();

    なんかの処理。ResultSetの中身取り出すとか。

} catch (なんかの例外) {
    例外の処理
} 

    ConnectionPreparedStatementtry-with-resourcesによって閉じられただろうけど、
    ResultSetは閉じられてるのかな…?

3. ResultSetは閉じられるのか?

そんなわけで、try-with-resourcesとPreparedStatementの場合、ConnectionとPreparedStatementはtryの後ろの括弧に入っていて閉じられる。でもResultSetはその括弧に入っていない。なのでResultSetは閉じられないのかと思いきや、Javaのドキュメントを確認すると

ResultSetオブジェクトは、このオブジェクトを生成したStatementオブジェクトが閉じられるとき、再実行されるとき、あるいは一連の複数の結果から次の結果を取り出すために使われるときに、自動的に閉じられます。

とうことで、Statementがtry-with-resouces構文で閉じられているので、結果的にResultSetも閉じられるっぽい(↓引用元ドキュメント)。
https://docs.oracle.com/javase/jp/8/docs/api/java/sql/ResultSet.html

Discussion