如何正确关闭资源

McG*_*one 18 java jdbc

当我清理一些代码时,FindBugs向我指出了一些使用Connection,CallableStatement和ResultSet对象的JDBC代码.这是该代码的片段:

CallableStatement cStmt = getConnection().prepareCall("...");
...
ResultSet rs = cStmt.executeQuery();

while ( rs.next() )
{
    ...
}

cStmt.close();
rs.close();
con.close();
Run Code Online (Sandbox Code Playgroud)

FindBugs指出这些应该在finally块中.我开始重构我的代码来执行此操作,我开始想知道如何处理finally块中的代码.

创建CallableStatement的Connection对象可能会抛出异常,将ResultSet对象保留为null.当我尝试关闭ResultSet时,我会得到一个NullPointerException,反过来,我的Connection将永远不会被关闭.实际上,这个线程提出了相同的概念,并表明将close()调用包装在null检查中是个好主意.

但是其他可能的例外呢?根据Java API规范,如果发生数据库错误,Statement.close()可以抛出SQLException.因此,即使我的CallableStatement不为null并且我可以在其上成功调用close(),我仍然可能会得到一个异常并且没有机会关闭我的其他资源.

我能想到的唯一"故障安全"解决方案是将每个close()调用包装在自己的try/catch块中,如下所示:

finally {

    try {
        cStmt.close();
    } catch (Exception e) { /* Intentionally Swallow  Exception */ }

    try {
        rs.close();
    } catch (Exception e) { /* Intentionally Swallow  Exception */ }

    try {
        con.close();
    } catch (Exception e) { /* Intentionally Swallow  Exception */ }

}
Run Code Online (Sandbox Code Playgroud)

男孩,如果那看起来不太可怕.有没有更好的方法来解决这个问题?

Edw*_*rzo 10

我认为已经提到了最佳答案,但我认为可以提及您可以考虑可自动分解资源的新JDK 7功能.

try{
    try(Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/hrdb", "obiwan", "kenobi"); 
        Statement stm = conn.createStatement(); 
        ResultSet rs = stm.executeQuery("select name from department")) {

        while(rs.next()){
            System.out.println(rs.getString("name"));
        }

    } 
}catch(SQLException e){
    //you might wanna check e.getSuppressed() as well
    //log, wrap, rethrow as desired.
}
Run Code Online (Sandbox Code Playgroud)

我们现在并不是所有人都可以迁移到JDK 7,但对于那些可以开始使用开发人员预览版的人来说,这提供了一种有趣的做事方式,当然可能会在不久的将来弃用其他方法.


maa*_*nus 5

如果可以的话,请使用Lombok 的 cleanup :

@Cleanup
Connection c = ...
@Cleanup
statement = c.prepareStatement(...);
@Cleanup
rs = statement.execute(...);
Run Code Online (Sandbox Code Playgroud)

这可以翻译为三个嵌套的 try-finally 块,并且可以正常工作,但有异常。没有充分的理由,永远不要吞下异常!

替代:

编写一个自己的实用方法,如下所示:

public static void close(ResultSet rs, Statement stmt, Connection con) throws SQLException {
    try {
        try {
            if (rs!=null) rs.close();
        } finally {
            if (stmt!=null) stmt.close();
        }
    } finally {
        if (con!=null) con.close();
    }
}
Run Code Online (Sandbox Code Playgroud)

并用在

try {
    Connection con = ...
    Statement stmt = ...
    ResultSet rs = ...
} finally {
    close(rs, stmt, con);
}
Run Code Online (Sandbox Code Playgroud)

并让异常冒泡或根据需要进行处理。

  • @maaartinus我不得不说,在MJB代码摘录中,异常不会被吞没,它们会被记录下来,这是完全不同的,并且在战略上是有效的,最重要的是,对于在 close 方法中发生的异常,你很可能不想这样做任何可以恢复的东西,恕我直言。 (2认同)