Подтвердить что ты не робот

Откат транзакции в SQLException с использованием нового блока try-with-resources

У меня проблема с try-with-ресурсами, и я прошу только быть уверенным. Могу ли я использовать его, если мне нужно реагировать на исключение, и мне все еще нужен ресурс в блоке 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
}

Я боюсь, что я все еще обречен использовать старый try-catch - наконец, в этом случае, даже в соответствии с документацией oracle - "catch и, наконец, блокирует оператор try-with-resources, выполняется любой блок catch или finally после того, как объявленные ресурсы были закрыты".

4b9b3361

Ответ 1

В соответствии с спецификацией языка соединение будет закрыто до того, как будет выполняться предложение 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.

Ответ 2

В вашем коде вы ловите "SQLException" для выполнения autoCommit reset. Любое исключение из среды выполнения (например, исключение нулевого указателя) будет выходить из вашего кода без сброса автоматической фиксации.

Синтаксис try-with-resource заставляет компилятор генерировать замечательный код для охвата всех путей выполнения и следить за всеми подавленными исключениями через закрытие. С помощью нескольких вспомогательных классов вы можете вставлять commit/rollback и reset -auto-commit в процесс генерации кода:

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);
    }

}

Теперь вы можете управлять откатом и autocommit с синтаксисом "попробуйте с ресурсом" следующим образом:

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

        // Do stuff

        tm.commit();
    } 

Ответ 3

    //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
        }
    }

Ответ 4

В приведенном выше примере я думаю, что лучше разместить con.commit() внутри вложенных try-catch, потому что он также может бросать SQLException.

 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);
        }
    }

У нас была такая проблема в нашей производственной среде с незакрытыми сеансами.