2013-02-13 3 views
16

Я пытаюсь создать метод, откуда я могу запросить мою базу данных и получить всю таблицу.Возврат 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; 
} 
+4

Почему вы не можете интерпретировать очевидное сообщение об ошибке - «* java.sql.SQLException: операция не разрешена после закрытия ResultSet *»? – Lion

ответ

40

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

+0

+1 для объяснения правильного пути (вместо обходных путей). – xyz

+0

Спасибо, что нашли время, чтобы написать подробный ответ. Используя этот код, я должен был бы сделать метод для каждой таблицы. Это действительно лучший способ сделать это? –

+1

Да, когда вы придерживаетесь JDBC низкого уровня. Однако вы можете реорганизовать повторяющийся код шаблона в довольно высокую степень, например, Hibernate, сделанный десять лет назад. Нет, по моему скромному мнению, JPA - лучший способ. Это вопрос 'return em.createQuery (« SELECT b FROM Biler b », Biler.class) .getResultList();' oneliner. – BalusC

1

Вы закрытия 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() просто и надежно закрыть Соединить/выписка/Resultset кортеж (обработки аннулирует и исключения правильно)

7

Ну, вы сделать вызов rs.close() в вашем finally -блоком.

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

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

Есть по крайней мере три возможного решение:

  1. не закрывать (и результирующее соединение, ...) и требует вызывающего абонента для вызова отдельного «закрыть» метод.

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

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

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

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

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

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

+0

Я бы использовал третий вариант, но не могу понять, как составить список, содержащий всю информацию, независимо от выбранной таблицы. –

+0

@PatrickReck: зачем вы это делаете * независимо от выбранной таблицы *? Различные таблицы содержат разные типы данных. –

+1

Точно. То, что я хочу, - это единственный метод, из которого я могу передать вызов SQL и вернуть данные, полученные из вызова. Если бы я выбрал весь стол для автомобилей (id, model), я бы хотел, чтобы эти два вернулись. Если бы это была таблица Customer (id, name, adress, phone), я бы хотел, чтобы все эти данные были возвращены * с использованием того же метода * –

12

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

+2

Этот подход занимает больше памяти, поскольку мы создаем отдельную хэш-карту, чем просто иметь resultSet и не закрывать его? – ramu

3

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