使用新的try-with-resources块在SQLException上进行事务回滚

Jan*_*uby 43 java 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
}
Run Code Online (Sandbox Code Playgroud)

我担心在这种情况下,我仍然注定要使用旧的try-catch-finally,即使根据oracle文档 - "在try-with-resources语句中捕获并最终阻塞,任何catch或finally块都在资源之后运行声明已关闭."

Alf*_*Alf 49

根据语言规范,执行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);
}
Run Code Online (Sandbox Code Playgroud)

希望这说明了这一点.如果您打算在生产代码中使用它,这应该会有很大的改进.

例如,如果您使用的是连接池,则必须在获得连接时返回连接,因此con.setAutoCommit(true); 应该在finally子句中完成.这意味着外部资源尝试应该是传统的try-catch-finally.

编辑(2018年)

我看到有人对此发表评论,所以我想我会给它一个2018年的答复.我不是在Java中工作了,主要是在Scala中,Clojure中和科特林一直在努力,而这种代码没有经过测试,所以请将此视为只是另一个例子.但是,由于Java有lambdas,我认为以下方法要好得多.我在其他语言的生产代码中做过类似的事情.

在这种方法中,有一个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;
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

我希望有一些经过实战考验的图书馆在那里为你做这些东西,至少在这些其他语言中.

我看到有几个关于自动提交和连接池的评论.以上示例应该与连接来自哪个位置无关,也可以不是池,即仅将其设置为true,如果它是初始值.因此,如果从池中它是错误的,则不应该触及它.

关于尝试资源的最后一句话.我不认为它是一个非常好的抽象,所以我会在更复杂的场景中小心使用它.

  • 请注意,调用[`setAutoCommit`](http://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html#setAutoCommit-boolean-)会在任何挂起的事务上调用COMMIT.所以在更复杂的代码中要小心. (9认同)
  • 但是你只能捕获`SQLException`.如果发生未检查的异常,例如`NullPointerException`,该怎么办?您的交易是否永远不会被回滚? (3认同)

Chr*_*ell 19

在您的代码中,您正在捕获"SQLException"以执行autoCommit重置.任何类型的运行时异常(如空指针异常)都会从代码中冒出来而不重置自动提交.

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

}
Run Code Online (Sandbox Code Playgroud)

现在,您可以使用"try with resource"语法控制回滚和自动提交,如下所示:

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

        // Do stuff

        tm.commit();
    } 
Run Code Online (Sandbox Code Playgroud)

  • 需要将 try-resource 中的“,”替换为“;”。至少那是我想要的。 (3认同)
  • 有趣的代码,我没有花时间尝试AutoCloseable接口.这似乎是替换finally子句的好方法.在生产中,只需一次try-with-resource,代码就会简单得多.太好了! (2认同)

Jas*_*key 8

    //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
        }
    }
Run Code Online (Sandbox Code Playgroud)


小智 5

在上面的示例中,我认为最好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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

我们的生产环境中存在这样的问题,会话不公开。

  • 您应该使用finally语句将自动提交设置回true。这将阻止您两次执行此语句。 (4认同)