環境
本記事を書くにあたって利用した主なソフトウェアのバージョンは次の通りです。
- javac 11.0.4
- openjdk version "11.0.4" 2019-07-16
- IBM DB2 Developer-C Edition 11.5 (Docker/wsl2)
- IBM Data Server Driver for JDBC and SQLJ 4.26.14
事象
以下のコードはEMPというテーブルからすべてのEMPNOを取得し、標準出力に出力するというものです。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class Main {
public static void main(String[] args) {
String url = "jdbc:db2://<hostname>:<port>/<dbname>";
String user = "<user>";
String password = "<password>";
try (Connection con = DriverManager.getConnection(url, user, password)) {
con.setAutoCommit(false);
PreparedStatement ps = con.prepareStatement("SELECT * FROM EMP");
ResultSet rs = ps.executeQuery();
while (rs.next()) {
System.out.println(rs.getString("EMPNO"));
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
このソースコードをコンパイルし、実行すると、標準出力にすべてのEMPNOを出力したあと、次のような例外が発生します。
com.ibm.db2.jcc.am.SqlException: [jcc][t4][10251][10308][4.26.14] 接続でのトランザクション進行中に java.sql.Connection.close() が要求されました。
トランザクションはアクティブのままとなり、接続はクローズできません。 ERRORCODE=-4471, SQLSTATE=null
at com.ibm.db2.jcc.am.b7.a(b7.java:794)
at com.ibm.db2.jcc.am.b7.a(b7.java:66)
at com.ibm.db2.jcc.am.b7.a(b7.java:133)
at com.ibm.db2.jcc.am.Connection.checkForTransactionInProgress(Connection.java:1484)
at com.ibm.db2.jcc.t4.b.checkForTransactionInProgress(b.java:7581)
at com.ibm.db2.jcc.am.Connection.closeResourcesX(Connection.java:1507)
at com.ibm.db2.jcc.am.Connection.closeX(Connection.java:1493)
at com.ibm.db2.jcc.am.Connection.close(Connection.java:1470)
at Main.main(Main.java:22)
java.sql.Connection.close()はtry-catch-with-resourceが勝手に呼び出してくれているから理解できるとして、そもそも「トランザクションはアクティブのままとなり、接続はクローズできません」ってなんのこっちゃ??? となるわけです。
原因
Db2ではDBコネクションをクローズする前に、COMMITやROLLBACKによってトランザクションを確定させる必要があります。COMMITやROLLBACKをするのは、INSERTやDELETEなど、DBのデータに変更を加えたときだけで、データを参照するだけのSELECTではCOMMITやROLLBACKに注意が回らない--という人も多いかと思いますが、少なくともDb2ではSELECTの場合でもトランザクションを意識する必要があります。
前述のソースコードでは、自動コミットがオフの状態になっていました(con.setAutoCommit(false))。自動コミット=trueの場合、Connection::closeを呼び出すと、jdbcドライバが自動でコミットしてくれるのですが、自動コミット=falseだと、プログラマが明示的にConnection::commitやConnection::rollbackを呼び出さないと、トランザクションが確定しません。つまり、前述のソースコードでは自動コミットをオフにしたために、トランザクションが確定しないまま、コネクションをクローズしようとし、その結果ERRORCODE=-4471が発生してしまったわけです。
対策
-
Connection::commitもしくはConnection::rollbackを呼び出して、トランザクションを明示的に完了させる - 自動コミット=trueにして、トランザクションの完了をJDBC Driverに丸投げする。
対策はこんなもんでしょうか(´・ω・`)