鳥小屋Blog

DDR、艦これ、東方、鉄道、そのほか日常いろいろ。

JDBCの暗黙のコミットとtry-with-resources構文

2017年04月22日 18時30分35秒 | PC・ネット・プログラミング
たまには技術っぽいことも書いてみますかねぇ。


Java 7で追加されたtry-with-resources構文というのがあります。
主にリソースの類を扱うときには必須といってもいいもの。
この構文を使うと、正常・例外発生など問わずブロックを抜けたとき自動でcloseしてくれるものですね。

発生した例外や、クローズ処理自体で発生した例外の扱いも
非常にうまいことやってくれたりしていてかなり助かります。
自分も積極的に利用しています。当然、DB接続を必要とするときとかにも。


自分は、仕事の関係でよくOracleのDBを使います。
OracleのJDBCドライバもよく使うことになります。
直接いじるんでなかったとしても、間接的には扱っている、と。

そのOracleのJDBCドライバには、DB接続クローズ時に暗黙のコミットが実行されるという仕様があります。
setAutoCommit()でDML実行時の自動コミットをしないよう設定しても、
このクローズ時の暗黙のコミットは実行されます。

クローズ時点で未コミットだった更新が反映されるわけです。
SQLを実行して、それがエラーなく実行できたのであれば、
最終的にはそれは反映されるべきもののはずで、この仕様自体は当然といえば当然です。


ただこの仕様、try-with-resource文を使うこと、業務仕様の都合によるトランザクション処理、
そのあたりを合わせて考えると、ちょっと注意が必要な場合がありそうかなと思いました。


仕様の都合上、複数のDML文で実現をすることがけっこうあります。
そのときは、データの整合性を保つため、複数のDMLやそれに絡む処理が全部成功したら初めてコミットでき、
DML文やそのまわりの処理で例外でもでたり、不都合なことがあったりしたなら、
ちゃんとロールバックしてやらなきゃならないということがままあります。


ところが、単純に以下のように書いてしまうと困ったことに。
try (Connection conn = ds.getConnection()) {
    conn.setAutoCommit(false);

    PreparedStatement pstmt = conn.prepareStatement(sqlHoge);    // DMLその1
    // 省略。パラメータ設定とか。
    pstmt.executeUpdate();

    PreparedStatement pstmt2 = conn.prepareStatement(sqlFuga);    // DMLその2
    // 省略。パラメータ設定とか。
    pstmt2.executeUpdate();

    conn.commit();
} catch(SQLException e) {
    // エラー処理(ロールバックしたい!)  ※
}

DMLその2で万一SQLExceptionなんかが起こってしまったりすると、
try-with-resourceのブロックを抜けてDB接続が自動クローズされます。
そのとき暗黙のコミットが走るので、DMLその1だけ反映された状態になってしまいます。

Oralce上でのエラーは、何が起こったとしてもSQLExceptionが投げられるようになってるので、
発生し次第、どうあろうとブロックを抜けてしまうわけです。

例外をキャッチした※のとこでなんとかしようとしても、この時点では暗黙のコミットは走ったあと。
DB接続もクローズ済み。そもそもスコープ外なので変数connにアクセスできず、なんともなりません。


うーん。困った。

下記のように、もいっこカン じゃなくて もいっこtryを重ねればロールバックできますが、
これあんまりかっこよくないですね…。
try (Connection conn = ds.getConnection()) {
    try {
        conn.setAutoCommit(false);

        PreparedStatement pstmt = conn.prepareStatement(sqlHoge);    // DMLその1
        // パラメータ設定とか?
        pstmt.executeUpdate();

        // なんか処理

        PreparedStatement pstmt2 = conn.prepareStatement(sqlFuga);    // DMLその2
        // パラメータ設定とか?
        pstmt2.executeUpdate();

        conn.commit();
    } catch(SQLException e) {
        // エラー処理
        conn.rollback();
    }
}

せっかくtry-with-resource文一本できれいだったのに、tryのために1段字下げすることに。
字下げもそうなんですが、tryを連続で2つというのが非常にもやもやします。

しますが、もっとシンプルできれいな方法というのも思いつきません。
こうするしかないのかなぁ。
コメント   この記事についてブログを書く
« 艦これ 2017年冬イベント まとめ | トップ | 2017年 艦これ春イベ準備号 »

コメントを投稿

PC・ネット・プログラミング」カテゴリの最新記事