web-dev-qa-db-ja.com

新しいtry-with-resourcesブロックを使用したSQLExceptionでのトランザクションロールバック

Try-with-resourcesに問題があり、念のために質問しています。例外に対応する必要があり、catchブロックのリソースが必要な場合、使用できますか?与えられた例はこれです:

try (Java.sql.Connection con = createConnection())
{
    con.setAutoCommit(false);
    Statement stm = con.createStatement();
    stm.execute(someQuery); // causes SQLException
}
catch(SQLException ex)
{
    con.rollback();
    // do other stuff
}

Oracleのドキュメントによれば、この場合でも古いtry-catch-finallyを使用する運命にあることを恐れています-"try-with-resourcesステートメントでcatchおよびfinallyブロック、catchまたはfinallyブロックはリソースの後に実行されます宣言されました。」

42
Jan Hruby

言語仕様によると、catch句が実行される前に接続が閉じられます( http://docs.Oracle.com/javase/specs/jls/se7/html/jls-14.html#jls- 14.20.3.2 )。

考えられる解決策は、try-with-resourcesステートメントをネストすることです。

try (Java.sql.Connection con = createConnection())
{
    con.setAutoCommit(false);
    try (Statement stm = con.createStatement())
    {
        stm.execute(someQuery); // causes SQLException
    }
    catch(SQLException ex)
    {
        con.rollback();
        con.setAutoCommit(true);
        throw ex;
    }
    con.commit();
    con.setAutoCommit(true);
}

うまくいけば、それがポイントを示しています。本番コードで使用する予定の場合は、これをかなり改善する必要があります。

たとえば、接続プールを使用している場合、接続を取得したとおりに返す必要があるため、con.setAutoCommit(true); finally節で行う必要があります。これは、外側のtry-with-resourcesが伝統的なtry-catch-finallyであることを意味します。

編集(2018)

まだこれについてコメントしている人がいるので、2018年に返信すると思いました。私はもうJavaで働いておらず、主にScala、Clojure、Kotlinで働いています。このコードはテストされていません。したがって、これを単なる例として扱ってください。ただし、Javaにはラムダがあるため、次のアプローチの方がはるかに優れていると思います。そして、私はこれらの他の言語の生産コードで同様のことをしました。

このアプローチには、厄介なトランザクションをすべて処理するinTransaction関数があります。しかし、使い方はとても簡単です。

public class Foo {

    interface ConnectionProvider {
        Connection get() throws SQLException;
    }

    public static <A> A doInTransation(ConnectionProvider connectionProvider, Function<Connection, A> f) throws SQLException {
        Connection connection = null;
        A returnValue;
        boolean initialAutocommit = false;
        try {
            connection = connectionProvider.get();
            initialAutocommit = connection.getAutoCommit();
            connection.setAutoCommit(false);
            returnValue = f.apply(connection);
            connection.commit();
            return returnValue;
        } catch (Throwable throwable) {
            // You may not want to handle all throwables, but you should with most, e.g.
            // Scala has examples: https://github.com/scala/scala/blob/v2.9.3/src/library/scala/util/control/NonFatal.scala#L1
            if (connection != null) {
                connection.rollback();
            }
            throw throwable;
        } finally {
            if (connection != null) {
                try {
                    if(initialAutocommit){
                        connection.setAutoCommit(true);
                    }
                    connection.close();
                } catch (Throwable e) {
                    // Use your own logger here. And again, maybe not catch throwable,
                    // but then again, you should never throw from a finally ;)
                    StringWriter out = new StringWriter();
                    e.printStackTrace(new PrintWriter(out));
                    System.err.println("Could not close connection " + out.toString());
                }
            }
        }
    }

    public static void main(String[] args) throws SQLException {
        DataSource ds = null;

        // Usage example:
        doInTransation(ds::getConnection, (Connection c) -> {
            // Do whatever you want in a transaction
            return 1;
        });
    }
}

私はあなたのためにこのようなことをやっているいくつかのバトルテストされたライブラリがあることを願っています、少なくともこれらの他の言語にはあります。

自動コミットと接続プールに関していくつかのコメントがあります。上記の例は、接続がどこから来たか、プールかどうかに関係なく、つまり、それが初期値であった場合にのみtrueに戻す必要があります。そのため、プールからそれが偽である場合、それは触れられるべきではありません。

Try-with-resourcesに関する最後の言葉。私はそれが非常に良い抽象化であるとは思わないので、より複雑なシナリオでそれを使用するよう注意します。

48
Alf

コードで、「SQLException」をキャッチしてautoCommitリセットを実行しています。あらゆる種類のランタイム例外(nullポインター例外など)は、自動コミットをリセットせずにコードからバブルします。

Try-with-resource構文により、コンパイラーはすべての実行パスをカバーし、クロージングを通じて抑制されたすべての例外に対応するための素晴らしいコードを生成します。いくつかのヘルパークラスを使用すると、コード生成プロセスにコミット/ロールバックとリセット自動コミットを挿入できます。

import Java.sql.SQLException;
import Java.sql.Connection;

public class AutoRollback implements AutoCloseable {

    private Connection conn;
    private boolean committed;

    public AutoRollback(Connection conn) throws SQLException {
        this.conn = conn;        
    }

    public void commit() throws SQLException {
        conn.commit();
        committed = true;
    }

    @Override
    public void close() throws SQLException {
        if(!committed) {
            conn.rollback();
        }
    }

}

public class AutoSetAutoCommit implements AutoCloseable {

    private Connection conn;
    private boolean originalAutoCommit;

    public AutoSetAutoCommit(Connection conn, boolean autoCommit) throws SQLException {
        this.conn = conn;
        originalAutoCommit = conn.getAutoCommit();
        conn.setAutoCommit(autoCommit);
    }

    @Override
    public void close() throws SQLException {
        conn.setAutoCommit(originalAutoCommit);
    }

}

次のような「リソースで試す」構文を使用して、ロールバックと自動コミットを制御できます。

    try(Connection conn = getConnection(),
        AutoSetAutoCommit a = new AutoSetAutoCommit(conn,false),
        AutoRollback tm = new AutoRollback(conn)) 
    {

        // Do stuff

        tm.commit();
    } 
19
ChrisCantrell
    //try with resources
    try(Connection conn = this.connectionProvider.getConnection()){//auto close BEFORE reach this , catch block, so we need a inner try block for statement
        boolean oldAutoCommit=conn.getAutoCommit();
        conn.setAutoCommit(false);//auto commit to false
        try(
            Statement stm = con.createStatement()
        ){
            stm.execute(someQuery); // causes SQLException
            conn.commit();//commit
        }
        catch (SQLException ex){
            conn.rollback();//error, rollback
            throw ex;//If you need to throw the exception to the caller
        }
        finally {
            conn.setAutoCommit(oldAutoCommit);//reset auto commit
        }
    }
8
Jaskey

上記の例では、SQLExceptionもスローできるため、ネストされた_try-catch_の中にcon.commit()を入れる方が良いと思います。

_ try (Java.sql.Connection con = createConnection())
    {
        con.setAutoCommit(false);
        try (Statement stm = con.createStatement())
        {
            stm.execute(someQuery); // causes SQLException
            con.commit();           // also causes SQLException!
        }
        catch(SQLException ex)
        {
            con.rollback();
            throw ex;
        }finally{
            con.setAutoCommit(true);
        }
    }
_

実稼働環境でセッションが閉じられていないため、このような問題が発生しました。

5
shambalaxx

Tryブロックの「con」参照を別のメソッド参照「connection」に割り当てることができます。

Java.sql.Connection connection = null;
try (Java.sql.Connection con = createConnection())
{
    connection  = con;
    con.setAutoCommit(false);
    Statement stm = con.createStatement();
    stm.execute(someQuery); // causes SQLException
}
catch(SQLException ex)
{
    connection.rollback();
    // do other stuff
}
0
GaripTipici