Подтвердить что ты не робот

Где закрыть соединение 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. Есть ли какое-либо решение?

4b9b3361

Ответ 1

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

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

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

con.close()

Ответ 2

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

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

Таким образом, ваш код SQL и DB остается там, где он принадлежит, ваша логика обработки набора результатов ближе к тому, где вы используете данные, и ваш код 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
      }
    });
  }

Ответ 3

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

Ответ 4

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

Ответ 5

Вы не можете использовать 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( );
  }
}

Ответ 6

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

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

Ответ 7

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

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

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

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

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

Ответ 8

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

Для использования с MySQL используйте эту версию: fooobar.com/questions/298540/...

Ответ 9

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

Ответ 10

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

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();
    }
}