我遇到了资源试用的问题,我要求确定一下。我是否可以使用它,如果我需要对异常做出反应,我仍然需要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文档 - “在try-with-resources语句中捕获并最终阻塞,任何catch或finally块都会运行在声明的资源关闭后。“
答案 0 :(得分:46)
根据语言规范,连接将在执行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-catch-finally。
编辑(2018)
我看到有人对此发表评论,所以我想我会给它一个2018年的答复。我不再使用Java了,主要是在Scala,Clojure和Kotlin工作,而且这段代码还没有经过测试,所以请把它作为另一个例子。但是,由于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;
});
}
}
我希望有一些经过测试的图书馆可以为你做这些东西,至少在这些其他语言中都有。
我看到有几个关于自动提交和连接池的注释。以上示例应该与连接来自哪个位置无关,即是否为池,即如果它是初始值,则仅将其设置为true。因此,如果从池中出现错误,则不应触及它。
关于尝试资源的最后一句话。我不认为它是一个非常好的抽象,所以我会在更复杂的场景中小心使用它。
答案 1 :(得分:18)
在您的代码中,您正在捕获“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);
}
}
现在,您可以使用“try with resource”语法控制回滚和自动提交,如下所示:
try(Connection conn = getConnection(),
AutoSetAutoCommit a = new AutoSetAutoCommit(conn,false),
AutoRollback tm = new AutoRollback(conn))
{
// Do stuff
tm.commit();
}
答案 2 :(得分:7)
//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
}
}
答案 3 :(得分: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);
}
}
我们的生产环境中存在这样的问题,并且有未闭合的会话。
答案 4 :(得分:0)
您可以将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
}