首页 > 解决方案 > 无法在 Java 中遍历 ResultSet

问题描述

我有个问题。我在我的 java 项目中使用以下 MySQL 驱动程序:

// SET THE MYSQL DRIVER
Class.forName("com.mysql.cj.jdbc.Driver");
SqlConn sqlConn = new SqlConn();

SqlConn课堂上,我有以下功能:

public ResultSet executeQuery(String query) {
    Statement stmt = null;
    ResultSet rs = null;
    try {
        stmt = conn.createStatement();

        if (stmt.execute(query)) {
            rs = stmt.getResultSet();
        }

        // Now do something with the ResultSet ....
    } catch (SQLException ex) {
        System.out.println("SQLException: " + ex.getMessage());
        System.out.println("SQLState: " + ex.getSQLState());
        System.out.println("VendorError: " + ex.getErrorCode());
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException ignored) {
            }
        }

        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException sqlEx) {
            } // ignore

            stmt = null;
        }
    }

    return rs;
}

该函数的使用如下:

ResultSet result = sqlConn.executeQuery("SELECT Market, Coin FROM Wallets GROUP BY Market, Coin ORDER BY Market, Coin;");

但是当我想像这样循环它时:

while (result.next()) {
    System.out.println(result.getString("Market"));
    System.out.println(result.getString("Coin"));
    System.out.println();
}

我收到以下错误:

Exception in thread "main" java.sql.SQLException: Operation not allowed after ResultSet closed
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129)
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97)
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:89)
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:63)
    at com.mysql.cj.jdbc.result.ResultSetImpl.checkClosed(ResultSetImpl.java:464)
    at com.mysql.cj.jdbc.result.ResultSetImpl.next(ResultSetImpl.java:1744)
    at com.company.drivers.SimulatorDriver.main(SimulatorDriver.java:82)

我做错了什么,我该如何解决?

标签: javamysqlsqlresultset

解决方案


在您粘贴的代码中,您首先创建一个连接,然后您无法关闭(资源泄漏!),然后您创建一个语句和一个结果集。在返回之前,您总是(通过 finally 块)关闭这些。因此,此方法创建一个 ResultSet,它立即关闭并返回这个现在关闭的结果集。

所有这些东西都是 20 多年前的想法。尝试用一些不错的方法来覆盖 JDBC 是完全有意义的,但绝对没有必要自己发明这个轮子;使用为您完成此操作的JOOQJDBI 。

如果您坚持使用此代码,请对此代码进行一些注释:

  1. 在管理资源时使用 try-with-resources,不要使用 finally 块。

  2. 所有 3 件事都需要关闭(Connection、Statement 和 ResultSet),但请注意 close() 传播:如果关闭结果集,则只需关闭结果集,但如果关闭语句,还会关闭它产生的任何结果集,并且如果你关闭一个连接,你也会依次关闭它产生的任何语句(以及所有结果集)。这使得编写这样的方法实际上是不可能的(你如何管理如何关闭事物)?因此,研究 lambdas。无论如何,您都希望使用 lambdas 重试。

  3. 声明几乎完全没用。当你有参数化查询时(即SELECT * FROM users WHERE username = ... username the user just entered into a web form here ...你不能使用任何一个:你不能在连接用户名的地方创建一个字符串,因为如果在表单上输入的用户名是whatever' OR 1 == 1; DROP TABLE users CASCADE; EXEC 'format C: /y';?不,唯一的方法to goPreparedStatement支持参数化,不会立即让您面临 SQL 注入攻击。

  4. 说真的,JOOQ 或 JDBI 做到了这一切,而且做得更好。

  5. 你不需要Class.forName("com.mysql.cj.jdbc.Driver");——已经 20 年不需要它了。

  6. 这种“白手起家,出结果集”的模式是行不通的。数据库有交易;有一个更大的上下文(事务),通常包含多个查询。您需要重新设计此 API 以考虑到这一点:或者将连接对象传递给 executeQuery 方法,或者让您自己的对象表示连接,并且它将具有查询方法。JOOQ 和 JDBI 也涵盖了这一点。假设非灾难性危险的隔离级别,即使是一系列只读查询也需要事务。


推荐阅读