Java 7自动资源管理JDBC(try-with-resources语句)

37

如何将常见的JDBC惯用语法:创建/接收连接,查询数据库并可能处理结果与Java 7的自动资源管理try-with-resources语句集成?(教程)

在Java 7之前,通常的模式类似于:

Connection con = null;
PreparedStatement prep = null;

try{
    con = getConnection();
    prep = prep.prepareStatement("Update ...");
    ...
    con.commit();
}
catch (SQLException e){
    con.rollback(); 
    throw e;
}
finally{
    if (prep != null)
        prep.close();
    if (con != null)
        con.close();
}

使用Java 7,您可以选择:

try(Connection con = getConnection(); PreparedStatement prep = con.prepareConnection("Update ..."){

   ...
   con.commit();
}

这将关闭ConnectionPreparedStatement,但是回滚事务怎么办?我不能在catch子句中添加包含回滚的代码,因为连接只在try块内可用。

您仍然将连接定义在try块外吗?在这里,特别是在使用连接池时,最佳实践是什么?


1
我在那些情况下不会使用自动关闭。正如术语所述,它仅用于关闭资源。顺便说一句:将连接放在 try... 块之外也无济于事,因为在 try 块之后你无法回滚,因为连接已经关闭了。 - home
可能是 https://dev59.com/GGsz5IYBdhLWcg3wLU12 的重复问题。 - Raedwald
3
@Raedwald:不,这不是重复的。这里是关于con.rollback()的。 - Bijan
3个回答

40
try(Connection con = getConnection()) {
   try (PreparedStatement prep = con.prepareConnection("Update ...")) {
       //prep.doSomething();
       //...
       //etc
       con.commit();
   } catch (SQLException e) {
       //any other actions necessary on failure
       con.rollback();
       //consider a re-throw, throwing a wrapping exception, etc
   }
}
根据oracle文档,您可以将try-with-resources块与常规try块组合使用。在我看来,上面的示例捕获了正确的逻辑,即:
  • 尝试关闭PreparedStatement,如果没有发生错误
  • 如果内部块中出现问题(无论是什么),回滚当前事务
  • 无论如何都要尝试关闭连接
  • 如果关闭连接时出现问题,则无法回滚事务(因为这是连接上的方法,现在处于不确定状态),所以不要尝试
在Java 6及更早版本中,我会使用三重嵌套的try块(外部try-finally,中间try-catch,内部try-finally)来实现此功能。ARM语法使代码更简洁。

1
你的解决方案在 SQLException 上回滚,但在 RuntimeExceptionError(等等)上没有回滚,这有点问题。基于 finally 的代码可以做到这一点。规则“不应该捕获Error”并不意味着在出现错误时回滚是可选操作。 - Piotr Findeisen
如果你想要那样做,那么就捕获 Throwable 而不是 SQLException。finally 不是回滚事务的正确位置,因为无论是否出现问题,finally 块都会被触发。当一切正常时,你绝对不想回滚! - Sean Reilly
是的,在try块中如果你在离开之前没有提交,那么就不会进行提交(在成功的情况下)。当然,在这种情况下,你需要提交-然后回滚-什么都不做,虽然这并不理想,但它绝对比在某些情况下缺少回滚要好。顺便说一句,我建议编辑答案,因为它是如此漂亮的复制粘贴代码,应该处理所有异常。 - Piotr Findeisen
@PiotrFindeisen 在 try 块中提交并在 finally 块中回滚是不正确的。即使一切正常,这也会导致额外的往返,这是一个显著的低效率。finally 块用于确保对象图处于一致状态(关闭连接、语句、流等),而不是执行仅在出现错误时才应发生的操作(回滚)。 - Sean Reilly
@SeanReilly 如果 PreparedStatement 的自动关闭引发异常怎么办?这不会触发不必要的回滚吗? - Enrique
@Enrique:根据文档,调用close()会抛出一个SQLException“如果发生数据库访问错误”。在这种情况下,事务确实处于未知状态,回滚是适当的。 - Sean Reilly

4

在我看来,在try-catch之外声明Connection和PreparedStatement是这种情况下可用的最佳方法。


2

如果你想在事务中使用池化连接,你应该按照以下方式使用:

try (Connection conn = source.getConnection()) {
        conn.setAutoCommit(false);
        SQLException savedException = null;
        try {
            // Do things with connection in transaction here...
            conn.commit();
        } catch (SQLException ex) {
            savedException = ex;
            conn.rollback();
        } finally {
            conn.setAutoCommit(true);
            if(savedException != null) {
                throw savedException;
            }
        }
    } catch (SQLException ex1) {
        throw new DataManagerException(ex1);
    }

这段示例代码处理设置自动提交值的问题。

需要注意的是,使用savedException可以保存异常,在conn.rollback()抛出另一个异常的情况下。这样,最终块将抛出“正确”的异常。


看起来,我们可以简化一下。为什么需要这么冗长的异常处理呢?我们可以使用以下代码:try (Connection conn = source.getConnection()) { conn.setAutoCommit(false); try {...; conn.commit(); } catch (SQLException ex) { conn.rollback(); throw ex; } finally { conn.setAutoCommit(true); } } - Igor Lytvynenko
有时候你想在不同的情况下抛出特定的DataManagerException(或其子类)异常。还要注意savedException变量。 - user3698328

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接