【问题标题】:Where to close a JDBC Connection while I want to return the ResultSet我想返回 ResultSet 时在哪里关闭 JDBC 连接
【发布时间】:2010-12-26 23:20:58
【问题描述】:

看来ResultSet在我关闭Connection时会自动关闭。 但是我想把ResultSet退回去用别的方法,然后不知道在哪里关闭ConnectionPreparedStatement

public ResultSet executeQuery(String sql, String[] getValue)
{
    Connection conn = null;
    PreparedStatement pstmt = null;
    ResultSet rs = null;
    try
    {
        conn = getConn();
        pstmt = conn.prepareStatement(sql);
        if (getValue != null)
        {
            for (int i = 0; i < getValue.length; i++)
            {
                pstmt.setString(i + 1, getValue[i]);
            }
        }
        rs = pstmt.executeQuery();
    } catch (Exception e)
    {
        e.printStackTrace();
        closeAll(conn, pstmt, rs);
    }
    return rs;
}

我已将closeAll(conn, pstmt, null); 移动到 catch 块中,因为我发现如果我将它放入 finally 块中,我将在它返回之前立即丢失我的 rs。 现在当我想关闭rs 时,我无法关闭connpstmt。有什么解决办法吗?

【问题讨论】:

  • 您是否尝试流式传输结果集,即避免简单地将所有结果读入某种集合并返回?
  • 题外话,为什么每个人都使用那种大括号样式?!!!这是 Java 而不是 C#
  • 谢谢你们所有的热心人!中国快到早上了,但我被你们和你们对睡眠的精彩回答所感动。这是我在 Stackoverflow.com 上的第一个问题。非常感谢你们的帮助。我将成为这里的常客!
  • @Oscar 这一定是 Jon Skeet 的错。我恨他:)

标签: java jdbc connection resultset


【解决方案1】:

断开连接后使用CachedRowSet保存信息

Connection con = ...
ResultSet rs = ...

CachedRowSet rowset = new CachedRowSetImpl();
rowset.populate(rs);

con.close()

【讨论】:

  • 在阅读了CachedRowSet的API之后,我发现这似乎是最简单的方法。我想我会接受这个答案。
【解决方案2】:

对此进行编码的一种简洁方法是传入一个对象,该对象具有一个接受结果集的回调方法。

您的其他方法使用回调方法及其结果集处理代码创建对象,并将其传递给执行 SQL 的方法。

这样,您的 SQL 和 DB 代码将保留在其所属的位置,您的结果集处理逻辑更接近您使用数据的位置,并且您的 SQL 代码在应该清理的时候进行清理。

  interface ResultSetCallBack{
    void handleResultSet(ResultSet r);
  }

  void executeQuery(..., ResultSetCallBack cb){
    //get resultSet r ...
    cb.handleResultSet(r);
    //close connection
  }

  void printReport(){
    executeQuery(..., new ResultSetCallBack(){
      public void handleResultSet(ResultSet r) {
        //do stuff with r here
      }
    });
  }

【讨论】:

    【解决方案3】:

    您应该永远不要ResultSet(或StatementConnection)传递给方法块之外的公众,在那里它们将被关闭避免资源泄漏。一种常见的做法是将ResultSet 映射到List&lt;Data&gt;,其中Data 只是一个表示感兴趣数据的javabean 对象。

    这是一个基本的例子:

    public class Data {
        private Long id;
        private String name;
        private Integer value;
        // Add/generate public getters + setters.
    }
    

    这是一个如何正确处理它的基本示例:

    public List<Data> list() throws SQLException {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        List<Data> list = new ArrayList<Data>();
    
        try {
            connection = database.getConnection();
            statement = connection.prepareStatement("SELECT id, name, value FROM data");
            resultSet = statement.executeQuery();
            while (resultSet.next()) {
                Data data = new Data();
                data.setId(resultSet.getLong("id"));
                data.setName(resultSet.getString("name"));
                data.setValue(resultSet.getInt("value"));
                list.add(data);
            }
        } finally {
            if (resultSet != null) try { resultSet.close(); } catch (SQLException logOrIgnore) {}
            if (statement != null) try { statement.close(); } catch (SQLException logOrIgnore) {}
            if (connection != null) try { connection.close(); } catch (SQLException logOrIgnore) {}
        }
    
        return list;
    }
    

    你可以按如下方式使用它:

    List<Data> list = dataDAO.list();
    

    要了解有关 JDBC 最佳实践的更多信息,您可能会发现 this basic kickoff article 也很有用。

    【讨论】:

    • 这需要将所有内容存储在内存中。当您有很多行时,除非您打算将数据保存在内存中,否则将未读行集传递给您想要对其执行的任何操作都非常有趣,无论是将其写入文件,过滤还是计算总和,并在处理时使用它。
    • @FlorianF 只需使用 LIMIT/OFFSET。
    • 这引入了很多复杂性,它增加了数据库访问并破坏了请求的原子性。您可能会得到重复,因为在两个“切片”请求之间的列表顶部删除了一行。
    • @FlorianF 只需使用 JPA。
    • 你能详细说明一下吗?
    【解决方案4】:

    按照您现在的方式,连接永远不会关闭,这会在以后(如果不是立即)给您的程序和 RDBMS 带来问题。最好创建一个 Java 类来保存 ResultSet 中的字段并将其返回。 ResultSet 链接到连接,因此无法返回它并关闭连接。

    【讨论】:

      【解决方案5】:

      关闭Connection 和/或PreparedStatement 后,您将无法使用ResultSet。 因此,您需要将一个对象传递给该方法,以在该对象上进行回调。

      所有清理都应在finally 块中完成。

      这样改写

      public ResultSet executeQuery(
          String sql,
          String[] getValue,
          CallbackObj cbObj
        ) throws SQLException
      {
        final Connection conn = getConn( );
      
        try
        {
          final PreparedStatement pstmt = conn.prepareStatement(sql);
      
          try
          {
            if (getValue != null)
            {
              for (int i = 0; i < getValue.length; i++)
              {
                pstmt.setString(i + 1, getValue[i]);
              }
            }
      
            final ResultSet rs = pstmt.executeQuery();
      
            try
            {
              cbObj.processResultSet( rs );
            }
            finally
            {
              // You may want to handle SQLException
              // declared by close
              rs.close( );
            }
          }
          finally
          {
            // You may want to handle SQLException
            // declared by close
            pstmt.close( );
          }
        }
        finally
        {
          // You may want to handle SQLException
          // declared by close
          conn.close( );
        }
      }
      

      【讨论】:

        【解决方案6】:

        当我想返回 ResultSet 时在哪里关闭 JDBC 连接

        实际上,您自己几乎已经回答了这个问题。正如您所试验的那样,关闭 Connection 将释放与其关联的 JDBC 资源(至少,事情应该是这样工作的)。所以,如果你想返回一个ResultSet(我稍后会回来),你需要“稍后”关闭连接。一种方法显然是将连接传递给您的方法,如下所示:

        public ResultSet executeQuery(Connection conn, String sql, String[] getValue);
        

        问题是我真的不知道你的最终目标是什么,为什么你需要这么低级的东西,所以我不确定这是一个好的建议。除非您正在编写低级 JDBC 框架(请不要告诉我您没有这样做),否则我实际上不建议返回 ResultSet。例如,如果您想提供一些业务类,请返回一些独立于 JDBC 的对象或它们的集合,就像其他人建议的那样,而不是 ResultSet。还要记住 RowSet ResultSet 所以如果你不应该使用ResultSet 那么你不应该使用RowSet

        就个人而言,我认为您应该使用一些辅助类而不是重新发明轮子。虽然 Spring 可能有点矫枉过正并且有一点学习曲线(如果你根本不知道,那就太多了),但 Spring 并不是唯一的方法,我强烈建议查看 Commons DbUtils。更具体地说,看看QueryRunner,尤其是这个query() 方法:

        public <T> T query(String sql,
                           ResultSetHandler<T> rsh,
                           Object... params)
                throws SQLException
        

        如您所见,此方法允许传递一个ResultSetHandler,它公开了一个回调方法以将ResultSets 转换为z5h's answer 中所述的其他对象,并且DbUtils 提供了几种实现,只需选择一个适合的实现即可您的需求。还可以查看DbUtils 类的实用方法,例如各种DbUnit.close(),您可能会发现它们可以方便地关闭 JDBC 资源。

        真的,除非您有充分的理由这样做(我很想知道它们),否则不要编写另一个 JDBC 框架,使用现有的解决方案,它会为您省去一些痛苦,更重要的是,一些错误,您将从经过验证的良好设计中受益。正如我们所见,即使对于低级别的东西,也有现有的(和简单的)解决方案。至少,检查一下。

        【讨论】:

          【解决方案7】:

          您可以调用ResultSet.getStatement 检索Statement,调用Statement.getConnection 检索Connection

          根据这些,您可以编写一个 closeResultSet 实用方法,该方法将为您关闭所有 3 个,只给出 ResultSet

          【讨论】:

            【解决方案8】:

            更简洁的方法是使用 CachedRowSetImpl。但在 MySQL 5.x+ 上,按名称或标签选择列存在一些错误。

            要与 MySQL 一起使用,请使用此版本: https://stackoverflow.com/a/17399059/1978096

            【讨论】:

              【解决方案9】:

              您真的不应该在较低级别处理 JDBC。请改用spring 之类的框架,它会为您处理所有必需的close() 操作。

              【讨论】:

              • 这是一个非常好的观点。使用像 Spring 这样的框架可以让您不必一直重新发明轮子并专注于制造汽车。
              • 不幸的是,在没有任何经验的情况下在新项目上设置 Spring 几乎注定要失败。
              • 确实如此。 JDBC 并不总是坏事。
              • 您不必“设置弹簧”。为了使用它的 JDBC 层,它只是一个额外的 jar。不需要额外的配置。
              • 我现在正在学习JDBC,是否可以跳过这部分学习Spring?
              【解决方案10】:

              我建议你做更多这样的事情:

              public List<Map> executeQuery(Connection connection, String sql) throws SQLException
              {
                  List<Map> rows = new ArrayList<Map>();
              
                  PreparedStatement stmt = null;
                  ResultSet rs = null;
              
                  try
                  {
                      pstmt = conn.prepareStatement(sql);
                      rs = stmt.execute();
                      int numColumns = rs.getMetaData().getColumnCount();
              
                      while (rs.next())
                      {
                          Map<String, Object> row = new LinkedHashMap<String, Object>();
                          for (int i = 0; i < numColumns; ++i)
                          {
                              String column = rs.getColumnName(i+1);
                              Object value = rs.getObject(i+1);
                              row.put(column, value);
                          }
                          rows.add(row);
                      }
                  } 
                  finally
                  {
                      close(rs);
                      close(stmt);
                  }
              
                  return rows;
              }
              
              public static void close(Statement s)
              {
                  try
                  {
                      if (s != null)
                      {
                          s.close();
                      }
                  }
                  catch (SQLException e)
                  {
                      e.printStackTrace();
                  }
              }
              
              public static void close(ResultSet rs)
              {
                  try
                  {
                      if (rs != null)
                      {
                          rs.close();
                      }
                  }
                  catch (SQLException e)
                  {
                      e.printStackTrace();
                  }
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2017-09-03
                • 1970-01-01
                • 2016-08-08
                • 2011-08-26
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2014-10-22
                相关资源
                最近更新 更多