Где закрыть соединение JDBC, когда я хочу вернуть ResultSet - PullRequest
18 голосов
/ 15 декабря 2009

Кажется, что 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. Есть ли решение?

Ответы [ 10 ]

31 голосов
/ 15 декабря 2009

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

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

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

con.close()
20 голосов
/ 15 декабря 2009

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

Ваш другой метод создает объект с методом обратного вызова с кодом обработки resultSet и передает его методу, выполняющему 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
      }
    });
  }
5 голосов
/ 15 декабря 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, вам может пригодиться эта базовая статья .

3 голосов
/ 01 июля 2013

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

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

3 голосов
/ 16 декабря 2009

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

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

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

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

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

3 голосов
/ 16 декабря 2009

Вы можете позвонить ResultSet.getStatement для получения Statement и Statement.getConnection для получения Connection.

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

3 голосов
/ 15 декабря 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( );
  }
}
3 голосов
/ 15 декабря 2009

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

1 голос
/ 16 декабря 2009

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

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();
    }
}
1 голос
/ 15 декабря 2009

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

...