Должны ли JDBC Resultsets и Statement быть закрыты отдельно, хотя впоследствии было закрыто Соединение?


256

Говорят, что это хорошая привычка - закрывать все ресурсы JDBC после использования. Но если у меня есть следующий код, нужно ли закрывать Resultset и Statement?

Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
    conn = // Retrieve connection
    stmt = conn.prepareStatement(// Some SQL);
    rs = stmt.executeQuery();
} catch(Exception e) {
    // Error Handling
} finally {
    try { if (rs != null) rs.close(); } catch (Exception e) {};
    try { if (stmt != null) stmt.close(); } catch (Exception e) {};
    try { if (conn != null) conn.close(); } catch (Exception e) {};
}

Вопрос в том, выполняет ли закрытие соединения работу или оставляет ли он некоторые ресурсы в использовании.


Ответы:


199

То, что вы сделали, является идеальной и очень хорошей практикой.

Причина, по которой я говорю, это хорошая практика ... Например, если по какой-то причине вы используете «примитивный» тип пула баз данных и вызываете connection.close(), соединение будет возвращено в пул, и ResultSet/ Statementникогда не будет закрыто, а затем вы столкнется с множеством разных новых проблем!

Так что вы не всегда можете рассчитывать на connection.close()уборку.

Надеюсь, это поможет :)


4
... и самая очевидная причина, чтобы закрыть все явно.
Зими

2
Я согласен с тем, что рекомендуется закрывать результирующие наборы и утверждения. Тем не менее, результирующие наборы и операторы являются сборщиком мусора - они не остаются открытыми вечно, и вы не «сталкиваетесь с множеством новых проблем».
Степанян

3
@ Ральф Стивенс - Вы не можете рассчитывать на это. У меня была ситуация, когда у драйвера MSSQL JDBC произошла утечка памяти, потому что ResultSet не были закрыты даже после сборки мусора.
Пол

7
@Paul - Интересно. Это звучит для меня как недостаток драйвера JDBC.
Степанян

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

124

Java 1.7 делает нашу жизнь намного проще благодаря выражению try-with-resources .

try (Connection connection = dataSource.getConnection();
    Statement statement = connection.createStatement()) {
    try (ResultSet resultSet = statement.executeQuery("some query")) {
        // Do stuff with the result set.
    }
    try (ResultSet resultSet = statement.executeQuery("some query")) {
        // Do more stuff with the second result set.
    }
}

Этот синтаксис довольно короткий и элегантный. И connectionдействительно будет закрыт, даже когда statementне может быть создан.


56
Вам не нужно так вкладывать, вы можете сделать все это за одну попытку с ресурсами, просто обработайте объявления ресурсов как отдельные операторы (разделенные ;)
Марк Роттвил

2
Mark Rotteveel: вы можете использовать одну попытку для всех трех Connection, Statement и ResultSet, но если вы хотите выполнить несколько запросов, вы должны закрыть предыдущий ResultSet перед началом нового запроса. По крайней мере, так работала СУБД, которую я использовал.
Рауль Салинас-Монтеагудо

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

Даниэль: Когда я использовал этот шаблон, базовый бэкэнд JDBC не поддерживал сохранение ResultSet открытым и открытие второго.
Рауль Салинас-Монтеагудо

rascio, вы можете делать все, что вам нужно в блоке ловли
Рауль Салинас-Монтеагудо

73

Из Javadocs :

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

Тем не менее, Javadocs не очень ясно, закрываются ли Statementи ResultSetпри закрытии базового Connection. Они просто утверждают, что закрывают соединение:

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

На мой взгляд, всегда явно близко ResultSets, Statementsи Connectionsкогда вы закончите с ними, реализация closeможет варьироваться между драйверами базы данных.

Вы можете сэкономить много лишнего кода, используя такие методы, как closeQuietlyв DBUtils от Apache.


1
Спасибо собачьи Дело в том, что вы не можете зависеть от реализации Connection.close, верно?
Zeemee


39

Я сейчас использую Oracle с Java. Вот моя точка зрения:

Вы должны закрыть ResultSetи Statementявно, потому что у Oracle ранее были проблемы с сохранением курсоров открытыми даже после закрытия соединения. Если вы не закроете ResultSet(курсор), он выдаст ошибку, как Превышено максимальное количество открытых курсоров .

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

Вот учебник Закрыть ResultSet, когда закончите :

Закрыть ResultSet, когда закончите

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

ResultSet.close();


Спасибо, Хилал, это веские причины, чтобы закрыть его как можно раньше. Однако имеет ли значение, если ResultSet и Statement закрываются непосредственно перед соединением (в некоторых случаях это означает: не так рано, как это возможно)?
Zeemee

Если вы закроете соединение, оно закроет все операторы набора результатов и оператора, но вы должны закрыть набор результатов до подключения

И почему я должен закрыть набор результатов перед соединением? Вы имеете ввиду из-за проблем с драйверами оракула?
Zeemee

1
вот более общее пояснение :) stackoverflow.com/questions/103938/…

Теоретически, если вы закрываете оператор, вам не нужно закрывать наборы результатов, но, вероятно, это хорошая практика.
rogerdpack

8

Если вы хотите более компактный код, я предлагаю использовать Apache Commons DbUtils . В таком случае:

Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
    conn = // Retrieve connection
    stmt = conn.prepareStatement(// Some SQL);
    rs = stmt.executeQuery();
} catch(Exception e) {
    // Error Handling
} finally {
    DbUtils.closeQuietly(rs);
    DbUtils.closeQuietly(stmt);
    DbUtils.closeQuietly(conn);
}

3
что произойдет, если я буду использовать этот код вместо rs.close (), stmt.close (), conn.close ()
Онкар Мусале

3

Правильный и безопасный метод для закрытия ресурсов, связанных с JDBC, это (взято из Как правильно закрыть ресурсы JDBC - каждый раз ):

Connection connection = dataSource.getConnection();
try {
    Statement statement = connection.createStatement();

    try {
        ResultSet resultSet = statement.executeQuery("some query");

        try {
            // Do stuff with the result set.
        } finally {
            resultSet.close();
        }
    } finally {
        statement.close();
    }
} finally {
    connection.close();
}

3

Неважно, если Connectionэто пул или нет. Даже соединение с бассейном должно быть очищено перед возвращением в бассейн.

«Очистить» обычно означает закрытие наборов результатов и откат любых ожидающих транзакций, но не закрытие соединения. В противном случае объединение теряет смысл.


2

Нет, вам не нужно ничего закрывать, НО соединение. Согласно спецификациям JDBC, закрытие любого более высокого объекта автоматически закроет более низкие объекты. Закрытие Connectionзакроет все Statements, которые установило соединение. Закрытие любого Statementзакроет все ResultSets, которые были созданы этим Statement. Неважно, если Connectionэто пул или нет. Даже соединение с бассейном должно быть очищено перед возвращением в бассейн.

Конечно, у вас могут быть длинные вложенные циклы при Connectionсоздании большого количества операторов, тогда закрытие их целесообразно. Я почти никогда не закрываюсь ResultSet, кажется чрезмерным при закрытии Statementили ConnectionБУДУ их закрывать.


1

Я создал следующий метод для создания многоразового использования One Liner:

public void oneMethodToCloseThemAll(ResultSet resultSet, Statement statement, Connection connection) {
    if (resultSet != null) {
        try {
            if (!resultSet.isClosed()) {
                resultSet.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    if (statement != null) {
        try {
            if (!statement.isClosed()) {
                statement.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    if (connection != null) {
        try {
            if (!connection.isClosed()) {
                connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

Я использую этот код в родительском классе, который унаследован для всех моих классов, отправляющих запросы к БД. Я могу использовать Oneliner для всех запросов, даже если у меня нет resultSet. Метод заботится о закрытии ResultSet, Statement, Connection в правильном порядке. Вот так выглядит мой наконец блок.

finally {
    oneMethodToCloseThemAll(resultSet, preStatement, sqlConnection);
}

-1

Насколько я помню, в текущем JDBC Resultsets и операторы реализуют интерфейс AutoCloseable. Это означает, что они автоматически закрываются после уничтожения или выхода из области видимости.


3
Нет, это только означает, что он closeвызывается в конце оператора try-with-resources. См. Docs.oracle.com/javase/tutorial/essential/exceptions/… и docs.oracle.com/javase/8/docs/api/java/lang/AutoCloseable.html .
Zeemee

-1

Некоторые удобные функции:

public static void silentCloseResultSets(Statement st) {
    try {
        while (!(!st.getMoreResults() && (st.getUpdateCount() == -1))) {}
    } catch (SQLException ignore) {}
}
public static void silentCloseResultSets(Statement ...statements) {
    for (Statement st: statements) silentCloseResultSets(st);
}

Здесь нет ничего, что могло бы закрыть что-либо. Просто бессмысленный цикл, который расточительно читает весь ответ, хотя он явно больше не нужен.
Маркиз Лорн

-1

С формой Java 6, я думаю, лучше проверить, закрыто она или нет перед закрытием (например, если какой-либо пул соединений высвобождает соединение в другом потоке) - например, некоторая проблема с сетью - оператор и состояние набора результатов могут быть закрыты. (это случается не часто, но у меня была эта проблема с Oracle и DBCP). Мой шаблон для этого (в старом синтаксисе Java):

try {
    //...   
    return resp;
} finally {
    if (rs != null && !rs.isClosed()) {
        try {
            rs.close();
        } catch (Exception e2) { 
            log.warn("Cannot close resultset: " + e2.getMessage());
        }
    }
    if (stmt != null && !stmt.isClosed()) {
        try {
            stmt.close();
        } catch (Exception e2) {
            log.warn("Cannot close statement " + e2.getMessage()); 
        }
    }
    if (con != null && !conn.isClosed()) {
        try {
            con.close();
        } catch (Exception e2) {
            log.warn("Cannot close connection: " + e2.getMessage());
        }
    }
}

Теоретически он не идеален на 100%, потому что между проверкой состояния закрытия и самим закрытием остается мало места для изменения состояния. В худшем случае вы получите предупреждение в течение длительного времени. - но это меньше, чем возможность изменения состояния в долгосрочных запросах. Мы используем этот шаблон в работе с «средней» нагрузкой (150 одновременных пользователей), и у нас не было с ним проблем - поэтому никогда не видели это предупреждающее сообщение.


Вам не нужны isClosed()тесты, потому что закрывать любой из них, который уже закрыт, нельзя. Что устраняет проблему окна синхронизации. Что также будет устранено путем создания переменных Connection, Statement, и ResultSetlocal.
Маркиз Лорн
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.