Les ensembles de résultats et les déclarations JDBC doivent-ils être fermés séparément, bien que la connexion soit fermée par la suite?

256

On dit que c'est une bonne habitude de fermer toutes les ressources JDBC après utilisation. Mais si j'ai le code suivant, est-il nécessaire de fermer l'ensemble de résultats et l'instruction?

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

La question est de savoir si la fermeture de la connexion fait le travail ou si elle laisse certaines ressources en cours d'utilisation.

Zeemee
la source
Duplication possible de la fermeture des connexions à la base de données en Java
Austin Schäfer

Réponses:

199

Ce que vous avez fait est une pratique parfaite et très bonne.

La raison pour laquelle je dis sa bonne pratique ... Par exemple, si pour une raison quelconque vous utilisez un type de pool de base de données "primitif" et que vous appelez connection.close(), la connexion sera renvoyée au pool et le ResultSet/ Statementne sera jamais fermé, puis vous va rencontrer de nombreux nouveaux problèmes!

Vous ne pouvez donc pas toujours compter sur connection.close()le nettoyage.

J'espère que ça aide :)

Paul
la source
4
... et la raison la plus évidente de tout fermer explicitement.
Zeemee
2
Je suis d'accord qu'il est recommandé de fermer les ensembles de résultats et les déclarations. Cependant, les jeux de résultats et les déclarations sont récupérés - ils ne restent pas ouverts pour toujours et vous ne rencontrez pas de nombreux nouveaux problèmes différents.
stepanian
3
@Ralph Stevens - Vous ne pouvez pas compter sur cela. J'ai eu une situation où le pilote MSSQL JDBC a perdu de la mémoire car les ResultSet n'étaient pas fermés, même après avoir été récupérés.
Paul
7
@Paul - Intéressant. Cela me semble être une lacune du pilote JDBC.
stepanian
2
@tleb - cela fonctionnerait comme prévu. bien qu'en théorie, les exceptions soient "chères", donc il y aurait un très petit coup de performance (que vous avez déjà identifié)
Paul
124

Java 1.7 nous simplifie la vie grâce à l' instruction 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.
    }
}

Cette syntaxe est assez brève et élégante. Et connectionsera en effet fermé même s'il statementn'a pas pu être créé.

Raúl Salinas-Monteagudo
la source
56
Vous n'avez pas besoin d'imbriquer comme ça, vous pouvez tout faire en un seul essai avec les ressources, il suffit de traiter les déclarations de ressources comme des instructions distinctes (séparées par ;)
Mark Rotteveel
2
Mark Rotteveel: vous pouvez utiliser un seul essai pour les trois Connection, Statement et ResultSet, mais si vous souhaitez effectuer plusieurs requêtes, vous devez fermer le ResultSet précédent avant de démarrer une nouvelle requête. C'est du moins ainsi que fonctionnait le SGBD que j'utilisais.
Raúl Salinas-Monteagudo
pourquoi ne faites-vous pas quelque chose comme ça? try (connexion ouverte) {try (plusieurs déclarations et ensembles de résultats) {surtout lorsque les résultats des requêtes suivantes peuvent être calculés avec les précédents.
Daniel Hajduk
Daniel: Lorsque j'ai utilisé ce modèle, le backend JDBC sous-jacent ne prenait pas en charge le maintien d'un ResultSet ouvert et l'ouverture d'un second.
Raúl Salinas-Monteagudo
rascio, vous pouvez faire tout ce dont vous avez besoin dans le bloc de capture
Raúl Salinas-Monteagudo
73

Depuis les javadocs :

Lorsqu'un Statementobjet est fermé, son ResultSetobjet actuel , s'il existe, est également fermé.

Cependant, les javadocs ne sont pas très clairs quant à savoir si Statementet ResultSetsont fermés lorsque vous fermez le sous-jacent Connection. Ils déclarent simplement que la fermeture d'une connexion:

Libère Connectionimmédiatement la base de données de cet objet et les ressources JDBC au lieu d'attendre leur libération automatique.

À mon avis, fermez toujours explicitement ResultSets, Statementset Connectionslorsque vous avez terminé avec eux car la mise en œuvre de closepeut varier entre les pilotes de base de données.

Vous pouvez vous sauver beaucoup de code de plaque chauffante en utilisant des méthodes telles que closeQuietlydans DBUtils d'Apache.

dogbane
la source
1
Merci dogbane. Le fait est que vous ne pouvez pas dépendre de l'implémentation de Connection.close, non?
Zeemee
1
note de côté pour n00bs comme moi - stackoverflow.com/questions/3992199/what-is-boilerplate-code
david blaine
39

J'utilise maintenant Oracle avec Java. Voici mon point de vue:

Vous devez fermer ResultSetet Statementexplicitement, car Oracle a précédemment rencontré des problèmes pour garder les curseurs ouverts même après la fermeture de la connexion. Si vous ne fermez pas le ResultSet(curseur), une erreur comme le nombre maximal de curseurs ouverts dépassés sera générée .

Je pense que vous pouvez rencontrer le même problème avec d'autres bases de données que vous utilisez.

Voici le didacticiel Fermer ResultSet une fois terminé :

Fermer ResultSet une fois terminé

Fermez l' ResultSetobjet dès que vous avez fini de travailler avec l' ResultSetobjet même si l' Statementobjet ferme l' ResultSetobjet implicitement lorsqu'il se ferme, la fermeture ResultSetdonne explicitement la possibilité au garbage collector de récupérer la mémoire le plus tôt possible car l' ResultSetobjet peut occuper beaucoup de mémoire en fonction de la requête.

ResultSet.close();

bleuâtre
la source
Merci hilal, ce sont de bonnes raisons de le fermer le plus tôt possible. Cependant, est-ce important si ResultSet et Statement sont fermés directement avant la connexion (cela signifie dans certains cas: pas le plus tôt possible)?
Zeemee
Si vous fermez la connexion, elle fermera également tous les résultats et l'instruction mais vous devez fermer le jeu de résultats avant la connexion
Et pourquoi devrais-je fermer le jeu de résultats avant la connexion? Vous voulez dire à cause des problèmes de pilote Oracle?
Zeemee
1
voici une clarification plus générale :) stackoverflow.com/questions/103938/…
En théorie, si vous fermez la déclaration que vous ne devez fermer les resultsets, mais il est probablement une bonne pratique.
rogerdpack
8

Si vous voulez du code plus compact, je suggère d'utiliser Apache Commons DbUtils . Dans ce cas:

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);
}
baron5
la source
3
que se passera-t-il si j'utilise ce code au lieu de rs.close (), stmt.close (), conn.close ()
Onkar Musale
3

La méthode correcte et sûre pour fermer les ressources associées à JDBC ceci (tiré de Comment fermer correctement les ressources JDBC - à chaque fois ):

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();
}
Prasanth Jayachandran
la source
3

Peu importe si Connectionest groupable ou non. Même la connexion groupable doit être nettoyée avant de retourner à la piscine.

"Nettoyer" signifie généralement la fermeture des ensembles de résultats et l'annulation de toutes les transactions en attente mais pas la fermeture de la connexion. Sinon, la mise en commun perd son sens.

Mad Calm
la source
2

Non, vous n'êtes pas obligé de fermer quoi que ce soit MAIS la connexion. Selon les spécifications JDBC, la fermeture de tout objet supérieur fermera automatiquement les objets inférieurs. La fermeture Connectionfermera tous les Statements créés par la connexion. La fermeture de tout Statementfermera tous les ResultSets créés par cela Statement. Peu importe si Connectionest groupable ou non. Même la connexion groupable doit être nettoyée avant de retourner à la piscine.

Bien sûr, vous pourriez avoir de longues boucles imbriquées sur la Connectioncréation de nombreuses instructions, puis les fermer est approprié. Je ne ferme presque jamais ResultSet, semble excessif lors de la fermeture Statementou les Connectionfermera.

Enerccio
la source
1

J'ai créé la méthode suivante pour créer une doublure réutilisable:

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

J'utilise ce code dans une classe parent qui est héritée de toutes mes classes qui envoient des requêtes DB. Je peux utiliser l'Oneliner sur toutes les requêtes, même si je n'ai pas de resultSet. La méthode s'occupe de fermer le ResultSet, Statement, Connection dans le bon ordre. Voici à quoi ressemble finalement mon bloc.

finally {
    oneMethodToCloseThemAll(resultSet, preStatement, sqlConnection);
}
Springe
la source
-1

Pour autant que je m'en souvienne, dans le JDBC actuel, les ensembles de résultats et les instructions implémentent l'interface AutoCloseable. Cela signifie qu'ils sont fermés automatiquement lorsqu'ils sont détruits ou hors de portée.

Mad Calm
la source
3
Non, cela signifie seulement que le closeest appelé à la fin d'une instruction try-with-resources. Voir docs.oracle.com/javase/tutorial/essential/exceptions/… et docs.oracle.com/javase/8/docs/api/java/lang/AutoCloseable.html .
Zeemee
-1

Quelques fonctions pratiques:

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);
}
Mad Calm
la source
Il n'y a rien ici qui ferme quoi que ce soit. Juste une boucle inutile qui lit inutilement la réponse entière, même si elle n'est clairement plus souhaitée.
Marquis de Lorne
-1

Avec Java 6, je pense qu'il vaut mieux vérifier qu'il est fermé ou non avant de fermer (par exemple si un pooleur de connexions expulse la connexion dans un autre thread) - par exemple un problème de réseau - l'état de la déclaration et du jeu de résultats peut être fermé. (ça n'arrive pas souvent, mais j'ai eu ce problème avec Oracle et DBCP). Mon modèle est pour cela (dans l'ancienne syntaxe 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());
        }
    }
}

En théorie, il n'est pas parfait à 100% car entre la vérification de l'état de fermeture et la fermeture elle-même, il y a un peu de place pour le changement d'état. Dans le pire des cas, vous recevrez un avertissement sous peu. - mais il est moindre que la possibilité de changement d'état dans les requêtes à long terme. Nous utilisons ce modèle en production avec une charge «moyenne» (150 utilisateurs simultanés) et nous n'avons eu aucun problème avec lui - alors ne voyez jamais ce message d'avertissement.

Csákány Róbert
la source
Vous n'avez pas besoin des isClosed()tests, car la fermeture de ceux qui sont déjà fermés est un no-op. Ce qui élimine le problème de la fenêtre de synchronisation. Ce qui serait également éliminé en créant les variables locales Connection, Statementet ResultSet.
Marquis de Lorne