返回ResultSet

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

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

我得到一个java.sql.SQLException: Operation not allowed after ResultSet closed在当前代码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; } 

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

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

 public List list() throws SQLException { Connection connection = null; PreparedStatement statement = null; ResultSet resultSet = null; List bilers = new ArrayList(); 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; } 

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

 public List list() throws SQLException { List bilers = new ArrayList(); 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; } 

顺便说一下,你根本不应该将ConnectionStatementResultSet Statement为实例变量(主要的线程安全问题!),也不应该在那时吞下SQLException (调用者将不知道发生了问题),也不要在同一个try关闭资源(例如,如果结果集关闭抛出exception,则语句和连接仍然打开)。 所有这些问题都在上面的代码片段中修复。

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

  List> resultList = new ArrayList>(); Map row = null; ResultSetMetaData metaData = rs.getMetaData(); Integer columnCount = metaData.getColumnCount(); while (rs.next()) { row = new HashMap(); for (int i = 1; i <= columnCount; i++) { row.put(metaData.getColumnName(i), rs.getObject(i)); } resultList.add(row); } 

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

好吧,你在你的finally rs.close()调用了rs.close()

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

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

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

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

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

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

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

    界面看起来像这样:

     public interface ResultSetConsumer { public T consume(ResultSet rs); } 

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

     public  List select(String query, ResultSetConsumer 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 result = new ArrayList(); 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; } 
  3. select方法中完成所有工作并返回一些List作为结果。

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

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

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; } 

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

  ComboPooledDataSource cpds = new ComboPooledDataSource(); try { cpds.setDriverClass(""); //loads the jdbc driver } catch (PropertyVetoException e) { e.printStackTrace(); return; } cpds.setJdbcUrl("jdbc:"); cpds.setMinPoolSize(5); cpds.setAcquireIncrement(5); cpds.setMaxPoolSize(20); javax.sql.DataSource dataSource = cpds; 

您可以使用适合您想要的CachedRowSet对象:

 public CachedRowSetImpl select(String url, String user, String password) { CachedRowSetImpl crs = null; try (Connection con = DriverManager.getConnection(url, user, password); Statement st = con.createStatement(); ResultSet rs = st.executeQuery("SELECT * FROM biler");) { crs = new CachedRowSetImpl(); crs.populate(rs); } catch (SQLException ex) { Logger lgr = Logger.getLogger(MySQL.class.getName()); lgr.log(Level.SEVERE, ex.getMessage(), ex); } catch (SQLException ex) { Logger lgr = Logger.getLogger(MySQL.class.getName()); lgr.log(Level.WARNING, ex.getMessage(), ex); } return crs; } 

你可以在这里阅读文档: https : //docs.oracle.com/javase/7/docs/api/javax/sql/rowset/CachedRowSet.html

您正在关闭ResultSet ,因此您无法再使用它。

为了返回表的内容,你必须遍历ResultSet并构建一个每行表示(在List ,也许?)。 据推测,每一行代表一个实体,我会为每一行创建这样一个实体。

 while (rs.next()) { list.add(new Entity(rs)); } return list; 

另一种方法是提供一些回调对象, ResultSet迭代将为每个ResultSet行调用该对象。 这样你就不需要构建一个代表整个表的对象(如果它的大小可能会有问题)

  while (rs.next()) { client.processResultSet(rs); } 

我不愿意让客户关闭结果集/语句/连接。 这些需要仔细管理以避免资源泄漏,并且你最好在一个地方处理它(最好靠近打开它们的地方!)。

注意:您可以使用Apache Commons DbUtils.closeQuietly()来简单可靠地关闭connect / statement / resultset元组(正确处理空值和exception)