在哪里关闭 java PreparedStatements 和 ResultSets?

Posted

技术标签:

【中文标题】在哪里关闭 java PreparedStatements 和 ResultSets?【英文标题】:Where to close java PreparedStatements and ResultSets? 【发布时间】:2010-09-24 04:47:20 【问题描述】:

考虑代码:

PreparedStatement ps = null;
ResultSet rs = null;
try 
  ps = conn.createStatement(myQueryString);
  rs = ps.executeQuery();
  // process the results...
 catch (java.sql.SQLException e) 
  log.error("an error!", e);
  throw new MyAppException("I'm sorry. Your query did not work.");
 finally 
  ps.close();
  rs.close();

上面没有编译,因为PreparedStatement.close()ResultSet.close() 都抛出了java.sql.SQLException。那么我应该在 finally 子句中添加一个 try/catch 块吗?或者将 close 语句移到 try 子句中?或者只是懒得打电话关闭?

【问题讨论】:

【参考方案1】:

在 Java 7 中,您不应显式关闭它们,而应使用 automatic resource management 来确保关闭 resources 并正确处理异常。异常处理是这样工作的:

尝试中的异常 |关闭异常 |结果 -----------------+--------------------+------------ ----------------------------------------- 没有 |没有 |继续正常 没有 |是 |抛出 close() 异常 是 |没有 |从 try 块中抛出异常 是 |是 |将 close() 异常添加到主异常 | |作为“抑制”,抛出主要异常

希望这是有道理的。在允许漂亮的代码,像这样:

private void doEverythingInOneSillyMethod(String key)
  throws MyAppException

  try (Connection db = ds.getConnection()) 
    db.setReadOnly(true);
    ...
    try (PreparedStatement ps = db.prepareStatement(...)) 
      ps.setString(1, key);
      ...
      try (ResultSet rs = ps.executeQuery()) 
        ...
      
    
   catch (SQLException ex) 
    throw new MyAppException("Query failed.", ex);
  


在 Java 7 之前,最好使用嵌套的 finally 块,而不是测试 null 的引用。

我将展示的示例在深度嵌套时可能看起来很难看,但在实践中,设计良好的代码可能不会以相同的方法创建连接、语句和结果;通常,每个级别的嵌套都涉及将资源传递给另一个方法,该方法将其用作另一个资源的工厂。使用这种方法,来自close() 的异常将掩盖来自try 块内部的异常。这是可以克服的,但它会导致代码更加混乱,并且需要一个自定义异常类来提供 Java 7 中存在的“被抑制”异常链。

Connection db = ds.getConnection();
try 
  PreparedStatement ps = ...;
  try 
    ResultSet rs = ...
    try 
      ...
    
    finally 
      rs.close();
    
   
  finally 
    ps.close();
  
 
finally 
  db.close();

【讨论】:

如果你把 finally 和右大括号放在同一行就不会那么难看了。 ;) Ctrl-Shift-F 随心所欲! ;) 不关闭语句是个坏主意。如果您使用连接池,您的代码将以ORA-01000: maximum open cursors exceeded 结尾。关闭语句会有所帮助,因为 Oracle 通用连接池 (UCP) 也执行语句池。 @ceving 没有人建议可以避免结束陈述。你在回应什么? @roomsg 确实如此,但如果您使用PreparedStatementResultSet 对象很可能在循环内创建,因此确保它们像这样关闭仍然是很好的代码卫生。如果您在关闭准备好的语句之前创建了很多结果集,您仍然会遇到问题,即使它们最终会被关闭。【参考方案2】:

如果您真的要手动滚动自己的 jdbc,它肯定会变得一团糟。 finally 中的 close() 需要用自己的 try catch 包裹起来,这至少是丑陋的。您不能跳过关闭,尽管在连接关闭时资源将被清除(如果您使用的是池,则可能不会立即清除)。实际上,使用框架(例如休眠)来管理您的数据库访问的主要卖点之一是管理连接和结果集处理,这样您就不会忘记关闭。

你可以做一些像这样简单的事情,这至少隐藏了混乱,并保证你不会忘记一些东西。

public static void close(ResultSet rs, Statement ps, Connection conn)

    if (rs!=null)
    
        try
        
            rs.close();

        
        catch(SQLException e)
        
            logger.error("The result set cannot be closed.", e);
        
    
    if (ps != null)
    
        try
        
            ps.close();
         catch (SQLException e)
        
            logger.error("The statement cannot be closed.", e);
        
    
    if (conn != null)
    
        try
        
            conn.close();
         catch (SQLException e)
        
            logger.error("The data source connection cannot be closed.", e);
        
    


然后,

finally 
    close(rs, ps, null); 

【讨论】:

如果你自己动手,这个解决方案效果很好,因为 close 方法抛出的 SQLExceptions 无论如何都是不可恢复的。另一种巧妙的方法是从该方法中抛出一个 RuntimeException 子类,该子类会冒泡到可以管理 DB 故障的代码层。 请注意,根据 JavaDoc for Statement,ResultSet 作为关闭语句的副作用而关闭,因此在此示例中并非绝对必要(但不会造成伤害)。 java.sun.com/javase/6/docs/api/java/sql/Statement.html#close() 我这样写是因为能够灵活地关闭一些对象为空是很有用的。因此,您可以使用相同的函数来关闭(语句、结果集、连接)对象的任何子集。 始终关闭语句,在连接关闭后让它们保持打开状态,可能会产生问题:在***.com/questions/321418/… 上查看更多信息。 刚刚又看了一遍 - 这是一个非常古老的答案,是用 Java6 天写的。如果相关对象支持,您可能可以使用 AutoCloseables 在 Java7 中更简洁地编写它。【参考方案3】:

对于文件 I/O,我通常在 finally 块中添加一个 try/catch。但是,您必须注意不要从 finally 块中抛出任何异常,因为它们会导致原始异常(如果有)丢失。

有关关闭数据库连接的更具体示例,请参阅this article。

【讨论】:

【参考方案4】:

不要浪费时间编写低级异常管理代码,使用 Spring-JDBC 等高级 API 或围绕连接/语句/rs 对象的自定义包装器来隐藏杂乱无章的 try-catch 代码。

【讨论】:

我也同意这一点。 Spring 会将已检查的异常包装成未检查的异常,因为在大多数情况下应用程序无法从数据库异常中恢复。【参考方案5】:

另请注意:

“当一个Statement对象被关闭时,它当前的ResultSet对象,如果存在的话,也被关闭。”

http://java.sun.com/j2se/1.5.0/docs/api/java/sql/Statement.html#close()

在 finally 中仅关闭 PreparedStatement 就足够了,并且仅当它尚未关闭时。但是,如果您想真正特别,请先关闭 ResultSet,而不是在关闭 PreparedStatement 之后(之后关闭它,就像这里的一些示例一样,实际上应该保证异常,因为它已经关闭了)。

【讨论】:

根据您引用的文档,在已关闭的 Statement 上调用 Statement.close() 无效。多次关闭 Statement 不会引发异常,因此按道理对 ResultSet 执行相同操作应该同样无害。 ResultSet 文档没有明确说明这一点,但也没有说您不应该多次关闭它......这个迂腐的咆哮的全部意义在于它不保证例外。虽然最好先关闭 ResultSet,以防某些实现出现这种情况。 我无法自拔。我必须指出,这是一个非常有用的答案,上面那个说“使用框架”的答案让我感到畏缩。【参考方案6】:

我通常有一个实用方法可以关闭这样的事情,包括注意不要尝试对空引用做任何事情。

通常如果close() 抛出一个我并不关心的异常,所以我只是记录异常并吞下它——但另一种选择是将它转换为RuntimeException。无论哪种方式,我都建议使用易于调用的实用方法来执行此操作,因为您可能需要在许多地方执行此操作。

请注意,如果关闭 PreparedStatement 失败,您当前的解决方案不会关闭 ResultSet - 最好使用嵌套的 finally 块。

【讨论】:

【参考方案7】:

以@erickson 的回答为基础,为什么不像这样在一个try 块中做呢?

private void doEverythingInOneSillyMethod(String key) throws MyAppException

  try (Connection db = ds.getConnection();
       PreparedStatement ps = db.prepareStatement(...)) 

    db.setReadOnly(true);
    ps.setString(1, key);
    ResultSet rs = ps.executeQuery()
    ...
   catch (SQLException ex) 
    throw new MyAppException("Query failed.", ex);
  

请注意,您不需要在try 块内创建ResultSet 对象,因为ResultSet 会在PreparedStatement 对象关闭时自动关闭。

当 Statement 对象时,ResultSet 对象会自动关闭 生成它的对象被关闭、重新执行或用于检索下一个 多个结果的序列。

参考:https://docs.oracle.com/javase/7/docs/api/java/sql/ResultSet.html

【讨论】:

【参考方案8】:

如果您使用的是 Java 7,则可以在实现 AutoCloseable 的类中使用异常处理机制的改进(即 PreparedStatementResultset

您可能还会发现这个问题很有趣:Closing ResultSet in Java 7

【讨论】:

【参考方案9】:

我知道这是一个老问题,但如果有人正在寻找答案,java 现在有 try-with-resouce 解决方案。

static String readFirstLineFromFile(String path) throws IOException 
      try (BufferedReader br =
                   new BufferedReader(new FileReader(path))) 
        return br.readLine();
    

【讨论】:

这与 Guido Garcia 的回答相同。 Try with resource 要求资源是 AutoCloseable。【参考方案10】:

不要省略调用 close。它可能会导致问题。

我更喜欢在 finally 中添加 try/catch 块。

【讨论】:

【参考方案11】:

可能是一种旧的(虽然简单)的做事方式,但它仍然有效:

public class DatabaseTest 

    private Connection conn;    
    private Statement st;   
    private ResultSet rs;
    private PreparedStatement ps;

    public DatabaseTest() 
        // if needed
    

    public String getSomethingFromDatabase(...) 
        String something = null;

        // code here

        try 
            // code here

         catch(SQLException se) 
            se.printStackTrace();

         finally  // will always execute even after a return statement
            closeDatabaseResources();
        

        return something;
    

    private void closeDatabaseResources() 
        try 
            if(conn != null) 
                System.out.println("conn closed");
                conn.close();
            

            if(st != null) 
                System.out.println("st closed");
                st.close();
            

            if(rs != null) 
                System.out.println("rs closed");
                rs.close();
            

            if(ps != null) 
                System.out.println("ps closed");
                ps.close();
            

         catch(SQLException se) 
            se.printStackTrace();
                       
    

【讨论】:

【参考方案12】:

focus finally 子句,

finally 
   try 
      rs.close();
      ps.close();
    catch (Exception e) 
      // Do something
   

我认为你必须修改 2 点。

首先,在fainlly子句中再次使用try & catch。

其次,在执行 ps.close() 之前执行 rs.close()。

fly1997@naver.com

【讨论】:

【参考方案13】:

我用这个..

finally

    if (ps != null) ps.close();
    if (rs != null) rs.close();

【讨论】:

这不能回答问题——因为 ps.close() 和 rs.close() 都可以抛出 SqlException。

以上是关于在哪里关闭 java PreparedStatements 和 ResultSets?的主要内容,如果未能解决你的问题,请参考以下文章

在哪里可以找到读取 vcard 文件的 java 库? [关闭]

在哪里可以找到 Java 中基于标准 Trie 的地图实现? [关闭]

在哪里可以找到 Java EE 6 的所有参考实现的列表? [关闭]

Java EE:我在哪里可以阅读有关 dao、服务以及为啥使用它们的信息? [关闭]

Java EE:我在哪里可以阅读有关 dao、服务以及为啥使用它们的信息? [关闭]

服务器端 Java - 从哪里开始 [关闭]