Где закрыть соединение JDBC, пока я хочу вернуть ResultSet

Кажется, что ResultSet будет автоматически закрыто, когда я закрою Connection. Но я хочу вернуть ResultSet и использовать его в другом методе, тогда я не знаю, где закрыть Connection и PreparedStatement.

public ResultSet executeQuery(String sql, String[] getValue)
{
    Connection conn = null;
    PreparedStatement pstmt = null;
    ResultSet rs = null;
    try
    {
        conn = getConn();
        pstmt = conn.prepareStatement(sql);
        if (getValue != null)
        {
            for (int i = 0; i < getValue.length; i++)
            {
                pstmt.setString(i + 1, getValue[i]);
            }
        }
        rs = pstmt.executeQuery();
    } catch (Exception e)
    {
        e.printStackTrace();
        closeAll(conn, pstmt, rs);
    }
    return rs;
}

Я переместил closeAll(conn, pstmt, null); в блок catch, потому что обнаружил, что если я помещу его в блок finally, я потеряю свой rs сразу же, как только он вернется. Теперь, когда я хочу закрыть rs, я не могу закрыть conn и pstmt. Есть ли решение?


person Aloong    schedule 15.12.2009    source источник
comment
вы пытаетесь передать набор результатов, т. е. не просто читать все результаты в какую-то коллекцию и возвращать ее?   -  person matt b    schedule 15.12.2009
comment
Не по теме, почему все используют этот стиль скобок?!!! Это Java, а не С#   -  person OscarRyz    schedule 15.12.2009
comment
Спасибо всем вам, сердечные! В Китае почти утро, но я слишком тронут вами и вашими блестящими ответами, чтобы заснуть. Это мой первый вопрос на Stackoverflow.com. Я очень ценю вашу помощь. Я буду здесь завсегдатаем!   -  person Aloong    schedule 15.12.2009
comment
@Oscar Должно быть, это вина Джона Скита. Я ненавижу его за это :)   -  person Pascal Thivent    schedule 16.12.2009


Ответы (10)


Используйте CachedRowSet для хранения информации после отключения

Connection con = ...
ResultSet rs = ...

CachedRowSet rowset = new CachedRowSetImpl();
rowset.populate(rs);

con.close()
person Mirek Pluta    schedule 15.12.2009
comment
Прочитав API CachedRowSet, я обнаружил, что это самый простой способ. Думаю, я приму этот ответ. - person Aloong; 16.12.2009

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

Ваш другой метод создает объект с помощью метода обратного вызова с его кодом обработки набора результатов и передает его методу, который выполняет SQL.

Таким образом, ваш код SQL и БД остается там, где он должен быть, ваша логика обработки наборов результатов ближе к тому месту, где вы используете данные, а ваш код SQL очищается, когда это необходимо.

  interface ResultSetCallBack{
    void handleResultSet(ResultSet r);
  }

  void executeQuery(..., ResultSetCallBack cb){
    //get resultSet r ...
    cb.handleResultSet(r);
    //close connection
  }

  void printReport(){
    executeQuery(..., new ResultSetCallBack(){
      public void handleResultSet(ResultSet r) {
        //do stuff with r here
      }
    });
  }
person z5h    schedule 15.12.2009

Вы должны никогда не передавать ResultSet (или Statement или Connection) в общий доступ за пределы блока методов, где они должны быть получены и закрыты во избежание утечки ресурсов. Обычной практикой является просто сопоставление ResultSet с List<Data>, где Data — это просто объект javabean, представляющий интересующие данные.

Вот простой пример:

public class Data {
    private Long id;
    private String name;
    private Integer value;
    // Add/generate public getters + setters.
}

и вот базовый пример того, как правильно с этим обращаться:

public List<Data> list() throws SQLException {
    Connection connection = null;
    PreparedStatement statement = null;
    ResultSet resultSet = null;
    List<Data> list = new ArrayList<Data>();

    try {
        connection = database.getConnection();
        statement = connection.prepareStatement("SELECT id, name, value FROM data");
        resultSet = statement.executeQuery();
        while (resultSet.next()) {
            Data data = new Data();
            data.setId(resultSet.getLong("id"));
            data.setName(resultSet.getString("name"));
            data.setValue(resultSet.getInt("value"));
            list.add(data);
        }
    } finally {
        if (resultSet != null) try { resultSet.close(); } catch (SQLException logOrIgnore) {}
        if (statement != null) try { statement.close(); } catch (SQLException logOrIgnore) {}
        if (connection != null) try { connection.close(); } catch (SQLException logOrIgnore) {}
    }

    return list;
}

вы можете использовать его следующим образом:

List<Data> list = dataDAO.list();

Чтобы узнать больше о передовом опыте работы с JDBC, вы можете найти это базовое введение статья тоже полезная.

person BalusC    schedule 15.12.2009
comment
Это требует хранить все в памяти. Когда у вас много строк, если вы не собираетесь хранить данные в памяти, действительно интересно передать непрочитанный набор строк тому, что вы хотите с ним сделать, будь то запись в файл, фильтрация или вычисление суммы, и потребляйте его по мере обработки. - person Florian F; 22.08.2017
comment
@FlorianF Просто используйте LIMIT/OFFSET. - person BalusC; 22.08.2017
comment
Это вносит большую сложность, умножает количество обращений к базе данных и нарушает атомарность запроса. Вы можете получить дубликаты, потому что строка была удалена в верхней части списка между двумя запросами на срезы. - person Florian F; 22.08.2017
comment
@FlorianF Просто используйте JPA. - person BalusC; 22.08.2017
comment
Можете ли вы уточнить это? - person Florian F; 25.08.2017

В том виде, в каком вы имеете это сейчас, соединение никогда не будет закрыто, что вызовет проблемы позже (если не сразу) для вашей программы и СУБД. Было бы лучше создать класс Java, чтобы хранить поля из ResultSet и возвращать их. ResultSet связан с соединением, поэтому его возврат и закрытие соединения невозможно.

person Andy Gherna    schedule 15.12.2009

Вы не можете использовать ResultSet после закрытия Connection и/или PreparedStatement. Итак, вам нужно передать объект, для которого нужно сделать обратный вызов, в этот метод.

Вся очистка должна выполняться в finally блоках.

Перепишите это так

public ResultSet executeQuery(
    String sql,
    String[] getValue,
    CallbackObj cbObj
  ) throws SQLException
{
  final Connection conn = getConn( );

  try
  {
    final PreparedStatement pstmt = conn.prepareStatement(sql);

    try
    {
      if (getValue != null)
      {
        for (int i = 0; i < getValue.length; i++)
        {
          pstmt.setString(i + 1, getValue[i]);
        }
      }

      final ResultSet rs = pstmt.executeQuery();

      try
      {
        cbObj.processResultSet( rs );
      }
      finally
      {
        // You may want to handle SQLException
        // declared by close
        rs.close( );
      }
    }
    finally
    {
      // You may want to handle SQLException
      // declared by close
      pstmt.close( );
    }
  }
  finally
  {
    // You may want to handle SQLException
    // declared by close
    conn.close( );
  }
}
person Alexander Pogrebnyak    schedule 15.12.2009

Где закрыть соединение JDBC, пока я хочу вернуть ResultSet

Собственно, вы почти сами и ответили на этот вопрос. Как вы уже экспериментировали, закрытие Connection освободить связанные с ним ресурсы JDBC (по крайней мере, так все должно работать). Итак, если вы хотите вернуть ResultSet (я вернусь к этому позже), вам нужно закрыть соединение «позже». Один из способов сделать это, очевидно, состоит в том, чтобы передать соединение с вашим методом, что-то вроде этого:

public ResultSet executeQuery(Connection conn, String sql, String[] getValue);

Проблема в том, что я на самом деле не знаю, какова ваша конечная цель и почему вам нужны вещи такого низкого уровня, поэтому я не уверен, что это хороший совет. Если вы не пишете структуру JDBC низкого уровня (и, пожалуйста, не говорите мне, что вы этого не делаете), я бы на самом деле не рекомендовал возвращать ResultSet. Например, если вы хотите накормить какой-то бизнес-класс, верните какой-нибудь независимый от JDBC объект или их набор, как рекомендовали другие, вместо ResultSet. Также имейте в виду, что RowSet < strong>является ResultSet поэтому, если вам не следует использовать ResultSet то вам не следует использовать RowSet .

Лично я думаю, что вам следует использовать какой-нибудь вспомогательный класс вместо того, чтобы изобретать велосипед. Хотя Spring может быть излишним и требует некоторой кривой обучения (слишком много, если вы вообще этого не знаете), Spring - не единственный путь, и я настоятельно рекомендую взглянуть на Commons DbUtils. В частности, посмотрите QueryRunner и особенно этот query() метод:

public <T> T query(String sql,
                   ResultSetHandler<T> rsh,
                   Object... params)
        throws SQLException

Как видите, этот метод позволяет передать ResultSetHandler который предоставляет метод обратного вызова для преобразования ResultSets в другие объекты, как описано в ответ z5h и DbUtils предоставляет несколько реализаций, просто выберите ту, которая соответствует вашим потребностям. Также взгляните на служебные методы DbUtils< /a>, например различные DbUnit.close(), которые могут оказаться удобными для закрытия ресурсов JDBC.

На самом деле, если у вас нет очень веских причин для этого (а мне было бы любопытно их узнать), не пишите еще одну JDBC-инфраструктуру, используйте существующее решение, это избавит вас от некоторых проблем и, что более важно, от некоторых ошибок. и вы выиграете от проверенного хорошего дизайна. Как мы видели, даже для вещей низкого уровня существуют (и простые) решения. По крайней мере, проверьте это.

person Pascal Thivent    schedule 16.12.2009

Вы можете вызвать ResultSet.getStatement, чтобы получить Statement, и Statement.getConnection, чтобы получить Connection.

Из них вы можете написать служебный метод closeResultSet, который закроет для вас все 3, не давая ничего, кроме ResultSet.

person Will Hartung    schedule 15.12.2009

Более чистый способ — использовать CachedRowSetImpl. Но в MySQL 5.x+ есть некоторые ошибки с выбором столбцов по имени или метке.

Для использования с MySQL используйте эту версию: https://stackoverflow.com/a/17399059/1978096

person Kai Burghardt    schedule 01.07.2013

Вы действительно не должны работать с JDBC на более низком уровне. Вместо этого используйте структуру типа spring. все необходимые close() операции за вас.

person tangens    schedule 15.12.2009
comment
Это очень хороший момент. Использование такой среды, как Spring, избавляет вас от необходимости постоянно изобретать велосипед и сосредоточиться на создании автомобиля. - person Alan Krueger; 15.12.2009
comment
К сожалению, настройка Spring в новом проекте без какого-либо предварительного опыта в значительной степени обречена на провал. - person Bombe; 15.12.2009
comment
конечно. JDBC не всегда плох. - person Bozho; 15.12.2009
comment
Вам не нужно настраивать весну. Для использования его слоя JDBC это просто дополнительная банка. Никаких дополнительных настроек не требуется. - person tangens; 15.12.2009
comment
Я изучаю JDBC прямо сейчас, можно ли просто пропустить эту часть и изучить Spring? - person Aloong; 15.12.2009
comment
@tangens Похоже, вам нужно сделать совсем немного, чтобы настроить Spring. Вот пример, который я видел для работы с данными одной таблицы - byteslounge.com/tutorials /spring-jdbc-транзакции-пример - person mikato; 19.11.2013

Я бы рекомендовал вам сделать что-то вроде этого:

public List<Map> executeQuery(Connection connection, String sql) throws SQLException
{
    List<Map> rows = new ArrayList<Map>();

    PreparedStatement stmt = null;
    ResultSet rs = null;

    try
    {
        pstmt = conn.prepareStatement(sql);
        rs = stmt.execute();
        int numColumns = rs.getMetaData().getColumnCount();

        while (rs.next())
        {
            Map<String, Object> row = new LinkedHashMap<String, Object>();
            for (int i = 0; i < numColumns; ++i)
            {
                String column = rs.getColumnName(i+1);
                Object value = rs.getObject(i+1);
                row.put(column, value);
            }
            rows.add(row);
        }
    } 
    finally
    {
        close(rs);
        close(stmt);
    }

    return rows;
}

public static void close(Statement s)
{
    try
    {
        if (s != null)
        {
            s.close();
        }
    }
    catch (SQLException e)
    {
        e.printStackTrace();
    }
}

public static void close(ResultSet rs)
{
    try
    {
        if (rs != null)
        {
            rs.close();
        }
    }
    catch (SQLException e)
    {
        e.printStackTrace();
    }
}
person duffymo    schedule 15.12.2009