Возврат ResultSet - программирование
Подтвердить что ты не робот

Возврат ResultSet

Я пытаюсь создать метод, из которого я могу запросить мою базу данных и получить целую таблицу.

В настоящее время он отлично работает, если я использую данные внутри метода. Однако я хочу, чтобы метод возвращал результаты.

Я получаю 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;
}
4b9b3361

Ответ 1

Вы не должны передавать 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 (если, например, закрытие результата закрывает исключение, то инструкция и соединение все еще открыты). Все эти проблемы исправлены в приведенных выше фрагментах кода.

Ответ 2

Если вы не знаете, что хотите от 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).

Ответ 3

Хорошо, вы do звоните rs.close() в свой finally -block.

Это в основном хорошая идея, так как вы должны закрыть все свои ресурсы (соединения, операторы, результирующие наборы,...).

Но вы должны закрыть их после их использования.

Существует как минимум три возможных решения:

  • не закрывать набор результатов (и соединение,...) и требовать, чтобы вызывающий вызывал отдельный метод "закрыть".

    Это в основном означает, что теперь вызывающему абоненту нужно помнить, что он звонит близко, и на самом деле не упрощает работу.

  • пусть вызывающий проходит в классе, который получает переданный набор результатов и вызывает это в вашем методе

    Это работает, но может стать немного подробным, поскольку для каждого блока кода, который вы хотите выполнить на наборе результатов, вам понадобится подкласс какого-либо интерфейса (возможно, как анонимный внутренний класс).

    Интерфейс выглядел следующим образом:

    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;
    }
    
  • выполните всю работу внутри метода select и верните в него List.

    Это, вероятно, наиболее широко используется: итерация по набору результатов и преобразование данных в пользовательские данные в собственные DTO и их возврат.

Ответ 4

Как все передо мной говорили о своей плохой идее передать результирующий набор. Если вы используете библиотеку пулов Connection, например 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;

Ответ 5

Вы закрываете 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 (обработка нулей и исключения)