24

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

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

我正在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;
}
4

8 回答 8

59

你永远不应该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;
}

或者,如果您已经使用 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;
}

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

于 2013-02-13T12:30:17.827 回答
16

如果您在检索时间时不知道您想要什么 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);
    }

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

于 2013-02-13T12:59:48.597 回答
8

好吧,你确实调用rs.close()了你的finally-block。

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

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

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

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

    这基本上意味着现在调用者需要记住调用 close 并没有真正让事情变得更容易。

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

    这可行,但可能会变得有点冗长,因为对于要在结果集上执行的每个代码块,您都需要某个接口的子类(可能作为匿名内部类)。

    界面如下所示:

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

    你的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;
    }
    
  3. 完成方法内的所有工作select并返回一些List结果。

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

于 2013-02-13T12:26:08.183 回答
4

正如我之前的每个人都说通过结果集是个坏主意。如果您使用像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;
}

下面是使用 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;
于 2014-06-21T18:20:12.847 回答
4

您可以使用 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

于 2018-03-17T15:52:47.900 回答
1

你正在关闭它ResultSet,因此你不能再使用它了。

为了返回表的内容,您必须遍历ResultSet并构建每行表示(在 a 中List,也许?)。大概每一行代表一些实体,我会为每一行创建一个这样的实体。

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

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

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

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

注意:您可以使用Apache Commons DbUtils.closeQuietly()简单可靠地关闭连接/语句/结果集元组(正确处理空值和异常)

于 2013-02-13T12:25:40.307 回答
0

假设您有能力将整个结果存储在内存中,您可以简单地返回一些类似表的结构。例如,使用Tablesaw,只需执行

Table t = Table.read().db(rows);

rows一个标准java.sql.ResultSet。有关详细信息,请参见此处。如果您打算进一步对数据进行切片和切块,Tablesaw 将变得特别有用,因为它为您提供了类似于 Pandas 的功能。

于 2020-02-24T11:27:49.403 回答
0

返回结果集是不好的做法,其次你已经关闭了它,所以在关闭它之后你就​​不能再使用它了。我建议在 try 块中使用具有多个资源的 Java 7 将帮助您,如上面建议的那样。如果你想要整个表的结果,你应该返回它的输出而不是结果集。

于 2020-02-24T11:33:23.923 回答