環境
本記事を書くにあたって利用した主なソフトウェアのバージョンは次の通りです。
- 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に丸投げする。
対策はこんなもんでしょうか(´・ω・`)