只在finally块中关闭JDBC连接?

5
为什么数据库连接经常在使用后直接关闭,然后在finally块中使用null检查再次关闭,以防止重复关闭。使用finally块不就足够了吗?finally块将在任何情况下执行。
这里是Apache-Tomcat JNDI Datasource HOW-TO的官方示例。他们指出每种情况下都必须关闭连接。我想知道为什么在主try {}块末尾的关闭命令似乎是多余的,使用finally块不够吗?
    Connection conn = null;
    Statement stmt = null; // Or PreparedStatement if needed
    ResultSet rs = null;
    try
    {
        conn = ... get connection from connection pool ...
        stmt = conn.createStatement("select ...");
        rs = stmt.executeQuery();
        ... iterate through the result set ...
        rs.close ();
        rs = null;
        stmt.close ();
        stmt = null;
        conn.close (); // Return to connection pool
        conn = null; // Make sure we don't close it twice
    }
    catch (SQLException e)
    {
        ... deal with errors ...
    }
    finally
    {
        // Always make sure result sets and statements are closed,
        // and the connection is returned to the pool
        if (rs != null)
        {
            try
            {
                rs.close ();
            }
            catch (SQLException ignore)
            {
            }
            rs = null;
        }
        if (stmt != null)
        {
            try
            {
                stmt.close ();
            }
            catch (SQLException ignore)
            {
            }
            stmt = null;
        }
        if (conn != null)
        {
            try
            {
                conn.close ();
            }
            catch (SQLException ignore)
            {
            }
            conn = null;
        }
    }

我想写得更短:

    Connection conn = null;
    Statement stmt = null; // Or PreparedStatement if needed
    ResultSet rs = null;
    try
    {
        conn = ... get connection from connection pool ...
        stmt = conn.createStatement ("select ...");
        rs = stmt.executeQuery();
        ... iterate through the result set ...
    }
    catch (SQLException e)
    {
        // ... deal with errors ...
    }
    finally
    {
        // Always make sure result sets and statements are closed,
        // and the connection is returned to the pool
        try
        {
            if (rs != null)
                rs.close ();
            if (stmt != null)
                stmt.close ();
            if (conn != null)
                conn.close ();
        }
        catch (SQLException ignore)
        {
        }
    }

1
这是我第一次看到这样的写法。我遇到的所有示例都只在 finally 块中执行。我认为这不是一个非常流行的做法。我认为它会给代码增加额外的负担和复杂性。 - Danail Alexiev
2
甚至可以使用 try-with-resources 更简短 ;) 但是,这个例子似乎过于复杂了。 - Marvin
2
如果你的代码中 rs.close() 抛出异常会怎么样?那么 stmtconn 将不会被关闭。 - Can't Tell
正确!如同 Apache 示例中的方式,应该将所有三个关闭命令放在三个独立的 try-catch 块中。 - tombo_189
3
Tomcat的示例代码质量较差,但您的改进并不明显。从Java 7开始,正确的解决方案是使用try-with-resources。 - user207421
不要忽略关闭Statement会关闭所有相关的ResultSet,关闭Connection会关闭所有相关的Statement...因此,你建议的finally块中三分之二都是多余的。 - user207421
3个回答

6
您有一个很好的问题 - 我也不理解"官方示例"。finally块肯定足够了。
然而,您的代码存在更严重的错误,即如果rs.close()抛出异常,您将泄漏stmt和conn并且还会默默地忽略该异常。这是您不应该做的事情。自Java 7以来,使用try-with-resources结构是首选方法,但如果您不能使用该方法,则至少单独处理每个可能的异常(rs、stmt、conn),以便它们不会互相泄漏。
例如,Apache Commons DbUtils专门为此目的提供了closeQuietly(),因为它曾经是一个常见的场景。个人建议去类似于Spring JDBCTemplate这样的地方,在幕后处理这些事情。
编辑:Oracle在这里解释了try-with-resources。简而言之,您可以像这样执行它:
try (Connection conn = yourCodeToGetConnection();
    Statement stmt = con.createStatement();
    ResultSet rs = stmt.executeQuery(query)) {
    while (rs.next()) {
        String coffeeName = rs.getString("COF_NAME");
        int supplierID = rs.getInt("SUP_ID");
        float price = rs.getFloat("PRICE");
        int sales = rs.getInt("SALES");
        int total = rs.getInt("TOTAL");

        System.out.println(coffeeName + ", " + supplierID + ", " + 
                               price + ", " + sales + ", " + total);
    }
} catch (SQLException ex) {
    // log, report or raise
}

try语句会自动处理connstmtrs的关闭,而且会按照你声明它们的相反顺序进行关闭。但是可能出现的异常仍需自行处理。


请问您能否解释一下try-with-resources结构?我之前还没有听说过。 - tombo_189
@tombo_189添加了一个快速解释。 - eis
我以前不知道有try with resources这个用法,谢谢你的分享。官方页面的代码对我来说也很难理解。 - Abhishek Singh
是的,连接本身应该在类似的块中,而且是的,这些东西可以合并到同一个try块中。这只是我快速组合的一个例子 - 我可以添加它们。 - eis
如何在JDBC中使用try-with-resources?提供了一些不错的想法,可以帮助你在需要PreparedStatement和绑定变量时完成此操作。 - Mick Mnemonic
显示剩余3条评论

0

感谢大家的许多评论。因此,总结一下(特别是EJP对我的问题的评论[关闭连接将关闭底层语句,关闭语句本身将关闭结果集]),由于我认为try-with-resource结构有点难以阅读,所以建议编写:

Connection conn = null;
Statement stmt = null; // Or PreparedStatement if needed
ResultSet rs = null;
try
{
    conn = ... get connection from connection pool ...
    stmt = conn.createStatement ("select ...");
    rs = stmt.executeQuery();
    ... iterate through the result set ...
}
catch (SQLException e)
{
    // ... deal with errors ...
}
finally
{
    // Always make sure result sets and statements are closed,
    // and the connection is returned to the pool
    try
    {
        if (conn != null)
            conn.close ();
    }
    catch (SQLException ignore)
    {
    }
}

只关闭主连接,而保留语句和结果集始终不变,并由连接关闭。

对吗?


这可能是一个有争议的话题,你可以在这个帖子中了解更多。通常情况下,Connection会关闭其他对象,但据我所知并不保证。我认为最好使用try-with-resources、JDBCTemplate或类似的方法来确保这些对象被关闭。但我也不否认这种做法的正确性。此外,个人建议至少记录任何SQLExceptions,即使您不引发它们。 - eis

0
我更喜欢编写一个通用的关闭连接方法,可以从 finally 块中调用。
就像这样:
Connection conn = null;        
Statement stmt = null; // Or PreparedStatement if needed        
ResultSet rs = null;

try
{
    conn = ... get connection from connection pool ...            
    stmt = conn.createStatement ("select ...");         
    rs = stmt.executeQuery();

    ... iterate through the result set ...

}
catch (SQLException e)
{
    // ... deal with errors ...
}
finally
{
    CloseTheConnection(conn);        
    CloseTheStatement(stmt);         
}

public void closeTheConnection(Connection conn){             
try{
    if(conn!=null){            
     conn.close();             
  }             
}catch(Exception ex){            
}

public void closeTheStatement(Statement stmt){             
try{            
 if( stmt != null)            
      stmt.close();            
  } catch(Exception ex){                
  }
}

通过创建不同的方法并从 finally 中调用它们,可以确保即使您从一个方法中获得任何异常,其他方法也一定会被调用。而且它也是可重复使用的。

注意你代码中的空格...我已经修复了代码块,但你应该自己修复空格以使其易读且一致。 - eis
好的,没问题。这是我在这里的第一个回答,甚至还在苦苦挣扎着如何发布 :D。我会注意的 :) - Vividh Kulshrestha

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