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