レガシーなAPI(JDBC接続)プログラム(Java 1.6時代)が無理やりSpring Bootに載せ替えられて動いていたのですが、本日不具合をみつけ、try-with-resources文を使って修正したので作業メモとして残します。
調べてみるとjava 7(2011年)からのようですが、try-with-resources が導入され、JDBC リソースを自動で close できるようになっています。この構文はJDBC専用というわけでなく、closeが必要なリソース=「AutoCloseableを実装したリソース」全般で利用できます。ここではjdbcを例に解説します
Java6以前
こんな感じでcloseがめんどいし、close漏れが何かと重大な不具合の元になっていました。
Connection conn = null;
PreparedStatement ps = null;
try {
conn = dataSource.getConnection();
conn.setAutoCommit(true);
ps = conn.prepareStatement("SELECT ...");
// 処理...
} catch (SQLException e) {
// エラーハンドリング
} finally {
// 閉じる順番を気にしたり、nullチェックが必要でとてもめんどい
if (ps != null) {
try { ps.close(); } catch (SQLException e) { /* スルー */ }
}
if (conn != null) {
try { conn.close(); } catch (SQLException e) { /* スルー */ }
}
}
Java7からはかなりスマートに
注意:JDBCの仕様として、setAutoCommit(true)がデフォルトなのであまり意識ないかもですが、setAutoCommit(false)にする場合は、これまで通りcommit/rollbackは自分で入れないといけません。あくまでcloseが省略されるだけです。
// try の後ろの ( ) の中でリソースを生成する
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement("SELECT ...")) {
// ここに本質的な処理を書くだけ!
} catch (SQLException e) {
// エラーハンドリング(クローズは「この前」に自動で終わっている)
}
// finally を書かなくても、スコープを抜けた時点で ps と conn が自動で close() される
tryの後ろを()で囲み、その中にはAutoCloseable(または Closeable)というインターフェースを実装しているクラスの変数宣言だけ(closeが必要なリソース)記載する事ができます。そのためconn.setAutoCommit(true); などをここに入れる事ができません。これを対策するには以下3つほど考えてみました。
案1 tryを2段構えとする。
せっかくシンプルになるところtryの2段構えはいまいちかなぁ、、
try (Connection conn = dataSource.getConnection()) {
// こだわり貫徹:psを作る「前」に設定を完了させる!
conn.setAutoCommit(true);
try (PreparedStatement ps = conn.prepareStatement("SELECT ...")) {
ps.executeQuery();
}
}
案2 気持ち悪いけどpreparestatementの後に設定する
executeの前に設定すればよいと割り切るか、、、
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement("SELECT ...")) {
// 妥協案:psを作った「後」、実行する「前」に設定する
conn.setAutoCommit(true);
ps.executeQuery();
}
案3 ヘルパーメソッドでごまかす(関数で包んでしまう)
try (Connection conn = enableAutoCommit(dataSource.getConnection()); // 関数で包む!
PreparedStatement ps = conn.prepareStatement("SELECT ...")) {
// ここに本質的な処理を書くだけ!
} catch (SQLException e) { ... }
// Connectionの設定を済ませてそのままConnectionを返すヘルパーメソッドを作成
private Connection enableAutoCommit(Connection conn) throws SQLException {
conn.setAutoCommit(true); // ここで設定変更
return conn; // 変更済みのconnをそのまま次に引き渡す
}
