在返回ResultSet时,我应该在哪里关闭JDBC连接?

20

看起来当我关闭Connection时,ResultSet将自动关闭。 但是我想在另一个方法中返回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。有什么解决方法吗?

你是否尝试流式传输resultSet,即避免仅读取所有结果并将其返回到某种集合中? - matt b
7
离题了,为什么每个人都使用那种大括号的样式?!这是Java不是C#。 - OscarRyz
谢谢你们所有热心的人!虽然现在已经快到中国的早晨了,但是你们和你们出色的回答让我太感动了,无法入睡。这是我在Stackoverflow.com上的第一个问题。非常感谢你们的帮助。我会成为这里的常客! - Aloong
@Oscar 这一定是 Jon Skeet 的错。因此我恨他 :) - Pascal Thivent
10个回答

33

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

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

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

con.close()

阅读完CachedRowSet的API后,我发现这似乎是最简单的方法。我想我会采用这个答案。 - Aloong

20

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

您的另一种方法是创建具有回调方法及其resultSet处理代码的对象,并将其传递给执行SQL的方法。

这样,您的SQL和数据库代码保持在它们应该在的位置,结果集处理逻辑更靠近使用数据的位置,并且在应该清理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
      }
    });
  }

5
您永远不应该在获取并关闭资源的方法块之外将ResultSet(或Statement或Connection)传递给公共方法,以避免资源泄漏。一种常见做法是将ResultSet映射到List,其中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的最佳实践,您可能会发现这篇基础教程文章也很有用。


这需要将所有内容存储在内存中。当你有大量的行时,除非你打算将数据保留在内存中,否则将未读取的行集传递给任何你想要处理它的东西是非常有趣的,无论是将其写入文件、过滤它还是计算总和,并在处理它时消耗它。 - Florian F
1
@FlorianF 只需要使用 LIMIT/OFFSET。 - BalusC
这会引入很多复杂性,增加数据库访问次数并破坏请求的原子性。由于在两个“切片”请求之间删除了列表顶部的行,因此可能会出现重复。 - Florian F
1
@FlorianF 只需使用JPA。 - BalusC
你能详细说明一下吗? - Florian F

3
在关闭 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( );
  }
}

3

当我想返回ResultSet时,应该在哪里关闭JDBC连接?

实际上,你几乎已经回答了这个问题。正如你所尝试的那样,关闭Connection将释放与其关联的JDBC资源(至少应该是这样的)。因此,如果你想返回一个ResultSet(稍后会再次提到),你需要“稍后”关闭连接。一种方法是在方法中传递连接,类似于以下代码:

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

问题在于我不知道你的最终目标是什么,以及为什么需要如此低级别的东西,所以我不确定这是否是一个好建议。除非你正在编写低级别的JDBC框架(请不要告诉我你不是这样做的),否则我实际上不建议返回ResultSet。例如,如果你想要提供一些业务类,可以返回一些与JDBC无关的对象或它们的集合,而不是ResultSet。还要记住,RowSetResultSet,所以如果你不应该使用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框架,使用现有的解决方案,这将节省您一些痛苦,更重要的是,避免出现错误,并且您将受益于经过验证的良好设计。即使是低级别的东西,也有现有的(简单)解决方案,就像我们所看到的那样。至少要去看看。

3

您现在的方式,连接将永远不会关闭,这将导致程序和关系型数据库以后(如果不是立即)出现问题。最好创建一个Java类来保存ResultSet中的字段并返回它。ResultSet与连接相关联,因此无法返回它并关闭连接。


3
你可以调用 ResultSet.getStatement 方法来获取 Statement,并使用 Statement.getConnection 方法获取 Connection
通过这些方法,你可以编写一个名为 closeResultSet 的实用程序方法,只需提供 ResultSet 就可以关闭所有 3 个对象。

3

1
我建议你做得更像这样:
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();
    }
}

1

在较低层次上处理JDBC是不好的。相反,使用像spring这样的框架,它将为您处理所有必需的close()操作。


这是一个非常好的观点。使用像Spring这样的框架可以避免你一直重复造轮子,而是专注于构建汽车。 - Alan Krueger
2
不幸的是,在没有任何先前经验的情况下,在新项目上设置Spring基本上注定会失败。 - Bombe
1
确实。JDBC并不总是不好的。 - Bozho
你不需要“设置Spring”。如果要使用它的JDBC层,只需添加一个额外的jar包即可。无需进行任何额外的配置。 - tangens
我现在正在学习JDBC,是否可以跳过这部分直接学习Spring? - Aloong
@tangens 看起来你需要做很多工作来设置Spring。这里有一个我看到的处理一张表数据的例子 - http://www.byteslounge.com/tutorials/spring-jdbc-transactions-example - Michael K

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