如何正确关闭资源

时间:2011-04-25 20:03:14

标签: java jdbc

当我清理一些代码时,FindBugs向我指出了一些使用Connection,CallableStatement和ResultSet对象的JDBC代码。以下是该代码的摘录:

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

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

cStmt.close();
rs.close();
con.close();

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

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

但其他可能的例外呢?根据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 */ }

}

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

6 个答案:

答案 0 :(得分: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.
}

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

答案 1 :(得分:3)

基本上这就是你要做的事情,除了你首先不一定吞下异常(你可以null检查并至少LOG例外)。其次,您可以使用

之类的东西设置一个很好的实用程序类
public static void close(ResultSet rs) {
   try { if (rs != null) rs.close();
   } catch (SQLException (e) {
      log.error("",e);
   } 

}

然后你只需要静态导入该类。

你终于会变成像

这样的东西
finally {
     close(resultset);
     close(statement);
     close(connection);
}

这真的不是那么可怕。

答案 2 :(得分:3)

如果可以,请使用Lombok's cleanup

@Cleanup
Connection c = ...
@Cleanup
statement = c.prepareStatement(...);
@Cleanup
rs = statement.execute(...);

这可以转换为三个嵌套的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();
    }
}

并在

中使用它
try {
    Connection con = ...
    Statement stmt = ...
    ResultSet rs = ...
} finally {
    close(rs, stmt, con);
}

让异常冒泡或根据需要处理它。

答案 3 :(得分:3)

您只需要关闭连接。

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

来自docs.oracle.com:

当垃圾收集时,Statement对象会自动关闭。关闭Statement对象时,其当前ResultSet对象(如果存在)也将关闭。

在Connection上调用close()会释放其数据库和JDBC资源。

答案 4 :(得分:2)

我知道隐藏所有丑陋的try-catch样板代码的唯一方法是使用类似Spring's JBDC Template的东西。

答案 5 :(得分:-2)

您可以将一个块包装到另一个块中:

try{
  Connection c = ...
    try{
      statement = c.prepareStatement(...);
      try{
        rs = statement.execute(...);
      }finally{
        rs.close();
      }
    }finally{
      statement.close()
    }
  }finally{
    c.close();
  }
}catch(SQLException e){}

将最下面的catch-block用于可能出现的所有内容

相关问题