【问题标题】:Where to close java PreparedStatements and ResultSets?在哪里关闭 java PreparedStatements 和 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 子句中?或者只是不打扰打电话关闭?

【问题讨论】:

    标签: java jdbc resource-management


    【解决方案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()
    • 我这样写是因为能够灵活地关闭一些对象为空是很有用的。因此,您可以使用相同的函数来关闭(语句、结果集、连接)对象的任何子集。
    • 始终关闭语句,在连接关闭后让它们保持打开状态,可能会产生问题:在stackoverflow.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 的回答相同。尝试使用资源要求资源可自动关闭。
            【解决方案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。
                  猜你喜欢
                  • 1970-01-01
                  • 2016-04-25
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2010-10-21
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多