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

У меня проблема с 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
}

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

4 ответов


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

надеюсь, это иллюстрирует точку зрения. Это должно быть немного улучшено, если вы планируете использовать его в производственном коде.

например, если вы используете пул соединений, то вы должны вернуть связь, как вы ее получили, так что Кон.setAutoCommit (true); должно быть сделано в предложении finally. Это означало бы, что внешняя попытка-с-ресурсами должна быть традиционной попыткой-поймать-наконец.


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

синтаксис 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);
    }

}

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

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

        // Do stuff

        tm.commit();
    } 

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

в приведенном выше примере я думаю, что лучше поставить 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);
        }
    }

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