返回ResultSet

Pat*_*eck 23 java mysql jdbc

我正在尝试创建一个方法,从中我可以查询我的数据库并检索整个表.

目前,如果我使用方法的数据,它的工作正常.但是,我希望该方法返回结果.

我正在java.sql.SQLException: Operation not allowed after ResultSet closed使用当前的代码.

我怎样才能做到这一点?

public ResultSet select() {

    con = null;
    st = null;
    rs = null;

    try {
        con = DriverManager.getConnection(url, user, password);
        st = con.createStatement();

        rs = st.executeQuery("SELECT * FROM biler");
        /*
        if (rs.next()) {
            System.out.println(rs.getString("model"));
        }*/

    } catch (SQLException ex) {
        Logger lgr = Logger.getLogger(MySQL.class.getName());
        lgr.log(Level.SEVERE, ex.getMessage(), ex);

    } finally {
        try {
            if (rs != null) {
                rs.close();
            }
            if (st != null) {
                st.close();
            }
            if (con != null) {
                con.close();
            }

        } catch (SQLException ex) {
            Logger lgr = Logger.getLogger(MySQL.class.getName());
            lgr.log(Level.WARNING, ex.getMessage(), ex);
        }
    }

    return rs;
}
Run Code Online (Sandbox Code Playgroud)

Bal*_*usC 51

你永远不应该ResultSet通过公共方法传递.这很容易导致资源泄漏,因为您不得不保持语句和连接打开.关闭它们会隐式关闭结果集.但是让它们保持打开会导致它们四处乱晃并导致数据库在资源过多时耗尽资源.

将它映射到Javabeans的集合,然后返回它:

public List<Biler> list() throws SQLException {
    Connection connection = null;
    PreparedStatement statement = null;
    ResultSet resultSet = null;
    List<Biler> bilers = new ArrayList<Biler>();

    try {
        connection = database.getConnection();
        statement = connection.prepareStatement("SELECT id, name, value FROM Biler");
        resultSet = statement.executeQuery();

        while (resultSet.next()) {
            Biler biler = new Biler();
            biler.setId(resultSet.getLong("id"));
            biler.setName(resultSet.getString("name"));
            biler.setValue(resultSet.getInt("value"));
            bilers.add(biler);
        }
    } finally {
        if (resultSet != null) try { resultSet.close(); } catch (SQLException ignore) {}
        if (statement != null) try { statement.close(); } catch (SQLException ignore) {}
        if (connection != null) try { connection.close(); } catch (SQLException ignore) {}
    }

    return bilers;
}
Run Code Online (Sandbox Code Playgroud)

或者,如果您已经使用Java 7,只需使用try-with-resources语句即可自动关闭这些资源:

public List<Biler> list() throws SQLException {
    List<Biler> bilers = new ArrayList<Biler>();

    try (
        Connection connection = database.getConnection();
        PreparedStatement statement = connection.prepareStatement("SELECT id, name, value FROM Biler");
        ResultSet resultSet = statement.executeQuery();
    ) {
        while (resultSet.next()) {
            Biler biler = new Biler();
            biler.setId(resultSet.getLong("id"));
            biler.setName(resultSet.getString("name"));
            biler.setValue(resultSet.getInt("value"));
            bilers.add(biler);
        }
    }

    return bilers;
}
Run Code Online (Sandbox Code Playgroud)

顺便说一句,你不应该声明Connection,StatementResultSet在所有的实例变量(主要threadsafety问题!),也被吞噬SQLException所有在这一点上(主叫方将不知道发生了问题),也被关闭同一资源try(如果结果集关闭抛出异常,则语句和连接仍然打开).所有这些问题都在上面的代码片段中修复.

  • 是的,当您坚持使用低级 JDBC 时。然而,您可以将重复的样板代码重构到相当高的程度,就像 Hibernate 十年前所做的那样。不,以我的拙见,JPA 是最好的方法。然后就是 `return em.createQuery("SELECT b FROM Biler b", Biler.class).getResultList();` oneliner 的问题。 (2认同)

小智 14

如果您在检索时间时不知道ResultSet的内容,我建议将完整的东西映射到这样的地图中:

    List<Map<String, Object>> resultList = new ArrayList<Map<String, Object>>();
    Map<String, Object> row = null;

    ResultSetMetaData metaData = rs.getMetaData();
    Integer columnCount = metaData.getColumnCount();

    while (rs.next()) {
        row = new HashMap<String, Object>();
        for (int i = 1; i <= columnCount; i++) {
            row.put(metaData.getColumnName(i), rs.getObject(i));
        }
        resultList.add(row);
    }
Run Code Online (Sandbox Code Playgroud)

所以基本上你和ResultSet有相同的东西(没有ResultSetMetaData).

  • 这种方法是否会占用更多内存,因为我们创建一个单独的hashMap而不仅仅是拥有resultSet而不是关闭它? (2认同)

Joa*_*uer 7

好了,你不要rs.close()你的finally-块.

基本上是个好主意,因为你应该关闭所有资源(连接,语句,结果集......).

但是你必须使用它们关闭它们.

至少有三种可能的解决方案:

  1. 不要关闭结果集(和连接,...)并要求调用者调用单独的"关闭"方法.

    这基本上意味着现在调用者需要记住调用close并且不会让事情变得更容易.

  2. 让调用者传入一个传递结果集的类,并在您的方法中调用它

    这可以工作,但可能会略显冗长,因为您需要为要在结果集上执行的每个代码块创建一些接口的子类(可能作为匿名内部类).

    界面看起来像这样:

    public interface ResultSetConsumer<T> {
      public T consume(ResultSet rs);
    }
    
    Run Code Online (Sandbox Code Playgroud)

    你的select方法看起来像这样:

    public <T> List<T> select(String query, ResultSetConsumer<T> consumer) {
      Connection con = null;
      Statement st = null;
      ResultSet rs = null;
    
        try {
          con = DriverManager.getConnection(url, user, password);
          st = con.createStatement();
    
          rs = st.executeQuery(query);
          List<T> result = new ArrayList<T>();
          while (rs.next()) {
              result.add(consumer.consume(rs));
          }
        } catch (SQLException ex) {
          // logging
        } finally {
          try {
            if (rs != null) {
                rs.close();
            }
            if (st != null) {
                st.close();
            }
            if (con != null) {
                con.close();
            }
          } catch (SQLException ex) {
            Logger lgr = Logger.getLogger(MySQL.class.getName());
            lgr.log(Level.WARNING, ex.getMessage(), ex);
          }
        }
      return rs;
    }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 完成方法中的所有工作select并返回一些List结果.

    这可能是使用最广泛的一个:迭代结果集并将数据转换为您自己的DTO中的自定义数据并返回它们.


hav*_*exz 6

正如我之前的每个人所说,传递结果集是一个坏主意。如果您使用像c3p0这样的连接池库,那么您可以安全地使用CachedRowSet及其实现CachedRowSetImpl。使用它您可以关闭连接。它只会在需要时使用连接。这是 java 文档的片段:

CachedRowSet 对象是断开连接的行集,这意味着它仅短暂使用与其数据源的连接。它在读取数据以用行填充自身时连接到其数据源,并在将更改传播回其基础数据源时再次连接。其余时间,CachedRowSet 对象会断开连接,包括在修改其数据时。断开连接使 RowSet 对象变得更加精简,因此更容易传递给另一个组件。例如,断开连接的 RowSet 对象可以被序列化并通过线路传递到瘦客户端,例如个人数字助理 (PDA)。

下面是查询并返回 ResultSet 的代码片段:

public ResultSet getContent(String queryStr) {
    Connection conn = null;
    Statement stmt = null;
    ResultSet resultSet = null;
    CachedRowSetImpl crs = null;
    try {
        Connection conn = dataSource.getConnection();
        stmt = conn.createStatement();
        resultSet = stmt.executeQuery(queryStr);

        crs = new CachedRowSetImpl();
        crs.populate(resultSet);
    } catch (SQLException e) {
        throw new IllegalStateException("Unable to execute query: " + queryStr, e);
    }finally {
        try {
            if (resultSet != null) {
                resultSet.close();
            }
            if (stmt != null) {
                stmt.close();
            }
            if (conn != null) {
                conn.close();
            }
        } catch (SQLException e) {
            LOGGER.error("Ignored", e);
        }
    }

    return crs;
}
Run Code Online (Sandbox Code Playgroud)

以下是使用 c3p0 创建数据源的代码片段:

 ComboPooledDataSource cpds = new ComboPooledDataSource();
            try {
                cpds.setDriverClass("<driver class>"); //loads the jdbc driver
            } catch (PropertyVetoException e) {
                e.printStackTrace();
                return;
            }
            cpds.setJdbcUrl("jdbc:<url>");
            cpds.setMinPoolSize(5);
            cpds.setAcquireIncrement(5);
            cpds.setMaxPoolSize(20);

 javax.sql.DataSource dataSource = cpds;
Run Code Online (Sandbox Code Playgroud)