Повинно чи закрити набори результатів JDBC та заяви окремо, хоча З'єднання після цього закрите?


256

Кажуть, що це добра звичка закривати всі ресурси JDBC після використання. Але якщо у мене є наступний код, чи потрібно закрити набір результатів та заяву?

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
... і найбільш очевидна причина закрити все явно.
Zeemee

2
Я погоджуюся з тим, що є гарною практикою закривати набори результатів та заяви. Однак набори результатів та заяви збирають сміття - вони не залишаються відкритими назавжди, і ви не «стикаєтеся з багатьма новими проблемами».
степанець

3
@Ralph Stevens - на це ти не можеш розраховувати. У мене виникла ситуація, коли драйвер MSSQL JDBC просочився пам'яттю, оскільки ResultSet не було закрито, навіть після збирання сміття.
Павло

7
@Paul - Цікаво. Це здається мені недоліком драйвера JDBC.
степанець

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

124

Java 1.7 значно полегшує наше життя завдяки випробуванню ресурсів .

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
Позначити Rotteveel: ви можете скористатись однією спробою для всіх трьох підключень, заяв та 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

1
примітка боку для n00bs , як я - stackoverflow.com/questions/3992199/what-is-boilerplate-code
Девід Блейн

39

Зараз я використовую Oracle з Java. Ось моя точка зору:

Вам слід закрити ResultSetі Statementявно, оскільки раніше у Oracle виникли проблеми з утримуванням курсорів відкритими навіть після закриття з'єднання. Якщо ви не закриєте ResultSet(курсор), це призведе до помилки, на кшталт перевищення Максимально відкритих курсорів .

Я думаю, що ви можете зіткнутися з тією ж проблемою і в інших базах даних, якими ви користуєтесь.

Ось підручник Close ResultSet, коли закінчите :

Закрийте ResultSet, коли закінчите

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

ResultSet.close();


Дякую хілалу, це вагомі причини, щоб закрити його якомога раніше. Однак чи має значення, якщо ResultSet і Statement закриваються безпосередньо перед Підключенням (це означає, що в деяких випадках: не якомога раніше)?
Zeemee

Якщо ви закриєте з’єднання, він також закриє всі набір результатів і оператор, але ви повинні закрити

І чому я повинен закрити набір результатів перед з'єднанням? Ви маєте на увазі через проблеми з драйвером Oracle?
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 ()
Onkar Musale

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закриє всі Statementстворені з’єднання. Закриття будь-якого Statementзакриє всі ResultSet, створені цим Statement. Не має значення, чи Connectionє об'єднаним чи ні. Навіть об'єднане з'єднання має очиститися перед поверненням у басейн.

Звичайно, у вас можуть бути довгі вкладені цикли на Connectionстворенні безлічі висловлювань, тоді їх закриття доречно. Я майже ніколи не закриваюсь ResultSet, здається надмірним при закритті Statementабо ConnectionБУДЕ їх закривати.


1

Я створив наступний метод для створення одноразового вкладиша:

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 для всіх запитів, навіть якщо у мене немає результатуSet.The Method забезпечує закриття ResultSet, Statement, Connection у правильному порядку. Ось так виглядає мій остаточний блок.

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

-1

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


3
Ні, це означає лише, що closeвиклик викликається в кінці випробуваного ресурсу. Див. 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і ResultSetлокальних змінних.
Маркіз Лорн
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.