返回ResultSet

时间:2013-02-13 12:23:49

标签: 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;
}

8 个答案:

答案 0 :(得分: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;
}

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

顺便说一句,您不应该将ConnectionStatementResultSet声明为实例变量(主要线程安全问题!),也不应吞下{{1}在那一点上(调用者将不知道发生了问题),也没有关闭同一SQLException中的资源(如果结果集关闭抛出异常,那么语句和连接仍然打开) 。所有这些问题都在上面的代码片段中修复。

答案 1 :(得分: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);
    }

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

答案 2 :(得分:7)

嗯,您 rs.close() - 阻止finally调用{/ 1}}。

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

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

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

  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中的自定义数据并返回那些

答案 3 :(得分:4)

正如我前面的每个人都说过传递结果集一个坏主意。如果您使用的是c3p0之类的连接池库,则可以安全地使用CachedRowSet及其实现CachedRowSetImpl。使用此功能,您可以关闭连接。它只会在需要时使用连接。这是来自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("<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;

答案 4 :(得分:3)

您可以使用适合您想要的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

答案 5 :(得分:1)

您正在关闭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元组(正确处理空值和异常)

答案 6 :(得分:0)

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

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

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

答案 7 :(得分:0)

返回结果集是一种不好的做法,其次您已经关闭了它,因此关闭它后就无法使用它了。 我建议将Java 7与try块中的多个资源一起使用将对您有所帮助,如上所述。 如果要获得整个表的结果,则应返回其输出而不是resultSet。