Где закрыть java PreparedStatements и ResultSets? - PullRequest
34 голосов
/ 26 ноября 2008

Рассмотрим код:

PreparedStatement ps = null;
ResultSet rs = null;
try {
  ps = conn.createStatement(myQueryString);
  rs = ps.executeQuery();
  // process the results...
} catch (java.sql.SQLException e) {
  log.error("an error!", e);
  throw new MyAppException("I'm sorry. Your query did not work.");
} finally {
  ps.close();
  rs.close();
}

Выше не компилируется, потому что оба PreparedStatement.close() и ResultSet.close() выдают java.sql.SQLException. Так я могу добавить блок try / catch в предложение finally? Или переместить оператор close в предложение try? Или просто не удосужились позвонить близко?

Ответы [ 13 ]

44 голосов
/ 26 ноября 2008

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

Exception in try | Exception in close | Result
-----------------+--------------------+----------------------------------------
      No         |        No          | Continue normally
      No         |        Yes         | Throw the close() exception
      Yes        |        No          | Throw the exception from try block
      Yes        |        Yes         | Add close() exception to main exception
                 |                    |  as "suppressed", throw main exception

Надеюсь, это имеет смысл. В позволяет красивый код, как это:

private void doEverythingInOneSillyMethod(String key)
  throws MyAppException
{
  try (Connection db = ds.getConnection()) {
    db.setReadOnly(true);
    ...
    try (PreparedStatement ps = db.prepareStatement(...)) {
      ps.setString(1, key);
      ...
      try (ResultSet rs = ps.executeQuery()) {
        ...
      }
    }
  } catch (SQLException ex) {
    throw new MyAppException("Query failed.", ex);
  }
}

До Java 7 лучше использовать вложенные блоки finally, а не проверять ссылки на null.

Пример, который я покажу, может показаться уродливым с глубоким вложением, но на практике хорошо спроектированный код, вероятно, не будет создавать соединение, оператор и результаты все в одном методе; часто каждый уровень вложенности включает передачу ресурса другому методу, который использует его как фабрику для другого ресурса. При таком подходе исключения из close() будут маскировать исключение внутри блока try. Этого можно преодолеть, но это приводит к еще более грязному коду и требует специального класса исключений, который обеспечивает «подавленную» цепочку исключений, присутствующую в Java 7.

Connection db = ds.getConnection();
try {
  PreparedStatement ps = ...;
  try {
    ResultSet rs = ...
    try {
      ...
    }
    finally {
      rs.close();
    }
  } 
  finally {
    ps.close();
  }
} 
finally {
  db.close();
}
25 голосов
/ 26 ноября 2008

Если вы действительно катаете свой собственный jdbc, он определенно становится грязным. В конце концов, close () в завершение должен быть заключен в собственный метод try, который, по крайней мере, ужасен. Вы не можете пропустить закрытие, хотя ресурсы будут очищены при закрытии соединения (что может быть не сразу, если вы используете пул). На самом деле, одним из главных преимуществ использования фреймворка (например, hibernate) для управления доступом к БД является управление соединением и обработкой результирующих наборов, чтобы вы не забыли закрыть.

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

public static void close(ResultSet rs, Statement ps, Connection conn)
{
    if (rs!=null)
    {
        try
        {
            rs.close();

        }
        catch(SQLException e)
        {
            logger.error("The result set cannot be closed.", e);
        }
    }
    if (ps != null)
    {
        try
        {
            ps.close();
        } catch (SQLException e)
        {
            logger.error("The statement cannot be closed.", e);
        }
    }
    if (conn != null)
    {
        try
        {
            conn.close();
        } catch (SQLException e)
        {
            logger.error("The data source connection cannot be closed.", e);
        }
    }

}

, а затем

finally {
    close(rs, ps, null); 
}
9 голосов
/ 26 ноября 2008

Для файлового ввода-вывода я обычно добавляю try / catch в блок finally. Однако вы должны быть осторожны, чтобы не создавать исключений из блока finally, поскольку они приведут к потере исходного исключения (если оно есть).

См. в этой статье для более конкретного примера закрытия соединения с базой данных.

7 голосов
/ 25 марта 2009

Также обратите внимание:

"Когда объект Statement закрыт, его текущий объект ResultSet, если таковой существует, также закрывается."

http://java.sun.com/j2se/1.5.0/docs/api/java/sql/Statement.html#close()

Должно быть достаточно закрыть только PreparedStatement в finally, и только если оно еще не закрыто. Однако если вы хотите быть очень конкретным, закройте ResultSet FIRST, а не после закрытия PreparedStatement (закрытие его после, как в некоторых примерах здесь, должно фактически гарантировать исключение, поскольку оно уже закрыто).

7 голосов
/ 26 ноября 2008

Не тратьте свое время на кодирование низкоуровневого управления исключениями, используйте высокоуровневый API, такой как Spring-JDBC, или пользовательскую оболочку вокруг объектов connection / Statement / rs, чтобы скрыть беспорядочный код, обработанный при попытке. 1001 *

5 голосов
/ 26 ноября 2008

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

Обычно, если close() выдает исключение, мне на самом деле все равно, поэтому я просто регистрирую исключение и проглатываю его - но другой альтернативой будет преобразование его в RuntimeException. В любом случае, я рекомендую делать это с помощью служебного метода, который легко вызывать, поскольку вам может потребоваться сделать это во многих местах.

Обратите внимание, что ваше текущее решение не закроет ResultSet, если закрыть PreparedStatement не удастся - лучше использовать вложенные блоки finally.

2 голосов
/ 06 августа 2012

Если вы используете Java 7, вы можете использовать улучшения в механизмах обработки исключений в тех классах, которые реализуют AutoCloseable (то есть PreparedStatement, Resultset)

Вам также может быть интересен этот вопрос: Закрытие ResultSet в Java 7

1 голос
/ 12 июня 2013

Я знаю, что это старый вопрос, но на тот случай, если кто-то ищет ответ, у java теперь есть решение попробовать с ресурсом.

static String readFirstLineFromFile(String path) throws IOException {
      try (BufferedReader br =
                   new BufferedReader(new FileReader(path))) {
        return br.readLine();
    }
}
0 голосов
/ 06 апреля 2017

Опираясь на ответ @ erickson, почему бы просто не сделать это в одном try блоке, как этот?

private void doEverythingInOneSillyMethod(String key) throws MyAppException
{
  try (Connection db = ds.getConnection();
       PreparedStatement ps = db.prepareStatement(...)) {

    db.setReadOnly(true);
    ps.setString(1, key);
    ResultSet rs = ps.executeQuery()
    ...
  } catch (SQLException ex) {
    throw new MyAppException("Query failed.", ex);
  }
}

Обратите внимание, что вам не нужно создавать объект ResultSet внутри блока try, поскольку ResultSet автоматически закрываются при закрытии объекта PreparedStatement.

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

Ссылка: https://docs.oracle.com/javase/7/docs/api/java/sql/ResultSet.html

0 голосов
/ 08 декабря 2014

Возможно, старый (хотя и простой) способ сделать что-то, но он все еще работает:

public class DatabaseTest {

    private Connection conn;    
    private Statement st;   
    private ResultSet rs;
    private PreparedStatement ps;

    public DatabaseTest() {
        // if needed
    }

    public String getSomethingFromDatabase(...) {
        String something = null;

        // code here

        try {
            // code here

        } catch(SQLException se) {
            se.printStackTrace();

        } finally { // will always execute even after a return statement
            closeDatabaseResources();
        }

        return something;
    }

    private void closeDatabaseResources() {
        try {
            if(conn != null) {
                System.out.println("conn closed");
                conn.close();
            }

            if(st != null) {
                System.out.println("st closed");
                st.close();
            }

            if(rs != null) {
                System.out.println("rs closed");
                rs.close();
            }

            if(ps != null) {
                System.out.println("ps closed");
                ps.close();
            }

        } catch(SQLException se) {
            se.printStackTrace();
        }               
    }
}
...