Müssen JDBC-Ergebnismengen und -Anweisungen separat geschlossen werden, obwohl die Verbindung danach geschlossen wird?

256

Es wird als gute Angewohnheit bezeichnet, alle JDBC-Ressourcen nach der Verwendung zu schließen. Aber wenn ich den folgenden Code habe, ist es notwendig, die Ergebnismenge und die Anweisung zu schließen?

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

Die Frage ist, ob das Schließen der Verbindung den Job erledigt oder ob einige Ressourcen verwendet werden.

Zeemee
quelle
Mögliches Duplikat des Schließens von Datenbankverbindungen in Java
Austin Schäfer

Antworten:

199

Was Sie getan haben, ist perfekt und sehr gute Praxis.

Der Grund, warum ich es als gute Praxis bezeichne ... Wenn Sie beispielsweise aus irgendeinem Grund einen "primitiven" Datenbankpooling-Typ verwenden und aufrufen connection.close(), wird die Verbindung zum Pool zurückgegeben und das ResultSet/ Statementwird niemals geschlossen und dann Sie wird auf viele verschiedene neue Probleme stoßen!

Sie können sich also nicht immer darauf verlassen connection.close(), aufzuräumen.

Ich hoffe das hilft :)

Paul
quelle
4
... und der offensichtlichste Grund, alles explizit zu schließen.
Zeemee
2
Ich stimme zu, dass es eine gute Praxis ist, Ergebnismengen und Aussagen zu schließen. Die Ergebnismengen und Anweisungen werden jedoch durch Müll gesammelt - sie bleiben nicht für immer offen und Sie stoßen nicht auf "viele verschiedene neue Probleme".
Stepanian
3
@ Ralph Stevens - Darauf können Sie nicht zählen. Ich hatte eine Situation, in der der MSSQL-JDBC-Treiber Speicher verloren hat, weil die ResultSets nicht geschlossen wurden, selbst nachdem Müll gesammelt wurde.
Paul
7
@ Paul - Interessant. Das klingt für mich nach einem Mangel des JDBC-Treibers.
Stepanian
2
@tleb - das würde wie erwartet funktionieren. Obwohl theoretisch Ausnahmen "teuer" sind, würde es einen sehr kleinen Leistungsverlust geben (den Sie bereits identifiziert haben)
Paul
124

Java 1.7 erleichtert unser Leben dank der Anweisung "Try-with-Resources" erheblich .

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.
    }
}

Diese Syntax ist sehr kurz und elegant. Und connectionwird in der Tat geschlossen, auch wenn das statementnicht erstellt werden konnte.

Raúl Salinas-Monteagudo
quelle
56
Sie müssen nicht so verschachteln, Sie können alles in einem Versuch mit Ressourcen ;
erledigen. Behandeln Sie
2
Mark Rotteveel: Sie können einen einzigen Versuch für alle drei Connection, Statement und ResultSet verwenden. Wenn Sie jedoch mehrere Abfragen ausführen möchten, müssen Sie das vorherige ResultSet schließen, bevor Sie eine neue Abfrage starten. Zumindest hat das von mir verwendete DBMS so funktioniert.
Raúl Salinas-Monteagudo
Warum machst du so etwas nicht? try (offene Verbindung) {try (mehrere Anweisungen und Ergebnismengen) {insbesondere, wenn die Ergebnisse der nächsten Abfrage mit den vorherigen berechnet werden können.
Daniel Hajduk
Daniel: Als ich dieses Muster verwendet habe, hat das zugrunde liegende JDBC-Backend nicht unterstützt, ein ResultSet offen zu halten und ein zweites zu öffnen.
Raúl Salinas-Monteagudo
Rascio, Sie können alles tun, was Sie im Fangblock brauchen
Raúl Salinas-Monteagudo
73

Aus den Javadocs :

Wenn ein StatementObjekt geschlossen wird, wird auch sein aktuelles ResultSetObjekt, falls vorhanden, geschlossen.

Die Javadocs sind sich jedoch nicht ganz sicher, ob die Statementund ResultSetgeschlossen sind, wenn Sie den Basiswert schließen Connection. Sie geben einfach an, dass das Schließen einer Verbindung:

Gibt Connectiondie Datenbank und die JDBC-Ressourcen dieses Objekts sofort frei, anstatt darauf zu warten, dass sie automatisch freigegeben werden.

Meiner Meinung nach immer explizit schließen ResultSets, Statementsund Connectionswenn Sie damit fertig sind, kann die Implementierung von closezwischen Datenbanktreibern variieren.

Mit Methoden wie closeQuietlyin DBUtils von Apache können Sie sich viel Kesselplattencode sparen .

Dogbane
quelle
1
Danke Dogbane. Der Punkt ist, dass Sie sich nicht auf die Implementierung von Connection.close verlassen können, oder?
Zeemee
1
Randnotiz für n00bs wie ich - stackoverflow.com/questions/3992199/what-is-boilerplate-code
David Blaine
39

Ich benutze jetzt Oracle mit Java. Hier mein Standpunkt:

Sie sollten schließen ResultSetund Statementexplizit, da Oracle zuvor Probleme hat, die Cursor auch nach dem Schließen der Verbindung offen zu halten. Wenn Sie den ResultSet(Cursor) nicht schließen , wird ein Fehler wie " Maximale Anzahl offener Cursor überschritten" ausgegeben .

Ich denke, dass Sie bei anderen von Ihnen verwendeten Datenbanken möglicherweise auf dasselbe Problem stoßen.

Hier ist das Tutorial Schließen Sie ResultSet, wenn Sie fertig sind :

Schließen Sie ResultSet, wenn Sie fertig sind

Schließen Sie das ResultSetObjekt, sobald Sie mit der Arbeit mit dem ResultSetObjekt fertig sind, obwohl das StatementObjekt das ResultSetObjekt beim Schließen implizit schließt. Durch das ResultSetexplizite Schließen kann der Garbage Collector den Speicher so früh wie möglich wiederherstellen, da das ResultSetObjekt je nach Abfrage möglicherweise viel Speicher belegt.

ResultSet.close();

bläulich
quelle
Dank hilal sind dies gute Gründe, es so früh wie möglich zu schließen. Ist es jedoch wichtig, ob ResultSet und Statement direkt vor der Verbindung geschlossen werden (dies bedeutet in einigen Fällen: nicht so früh wie möglich)?
Zeemee
Wenn Sie die Verbindung schließen, wird auch die gesamte Ergebnismenge und Anweisung geschlossen, aber Sie sollten die Ergebnismenge vor der Verbindung schließen
Und warum sollte ich die Ergebnismenge vor der Verbindung schließen? Du meinst wegen der Orakelfahrer Probleme?
Zeemee
1
Hier ist eine allgemeinere Klarstellung :) stackoverflow.com/questions/103938/…
In der Theorie, wenn Sie die Anweisung schließen müssen Sie nicht haben , um die Resultsets zu schließen, aber es ist wahrscheinlich eine gute Praxis.
Rogerdpack
8

Wenn Sie kompakteren Code wünschen, empfehle ich die Verwendung von Apache Commons DbUtils . In diesem Fall:

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
quelle
3
Was passiert, wenn ich diesen Code anstelle von rs.close (), stmt.close (), conn.close () verwende
Onkar Musale
3

Die richtige und sichere Methode zum Schließen der mit JDBC verknüpften Ressourcen (siehe So schließen Sie JDBC-Ressourcen jedes Mal richtig ):

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
quelle
3

Es spielt keine Rolle, ob ConnectionPooling möglich ist oder nicht. Sogar die Pool-Verbindung muss gereinigt werden, bevor Sie zum Pool zurückkehren.

"Bereinigen" bedeutet normalerweise, Ergebnismengen zu schließen und ausstehende Transaktionen zurückzusetzen, aber die Verbindung nicht zu schließen. Andernfalls verliert das Pooling seinen Sinn.

Mad Calm
quelle
2

Nein, Sie müssen nichts schließen, ABER die Verbindung. Gemäß den JDBC-Spezifikationen werden beim Schließen eines höheren Objekts automatisch niedrigere Objekte geschlossen. Durch das Schließen Connectionwerden alle von Statementder Verbindung erstellten s geschlossen. Wenn Sie eine Statementschließen, werden alle ResultSets geschlossen, die dadurch erstellt wurden Statement. Es spielt keine Rolle, ob ConnectionPooling möglich ist oder nicht. Sogar die Pool-Verbindung muss gereinigt werden, bevor Sie zum Pool zurückkehren.

Natürlich haben Sie möglicherweise lange verschachtelte Schleifen Connection, um viele Anweisungen zu erstellen. Dann ist es angebracht, diese zu schließen. Ich schließe fast nie ResultSet, scheine beim Schließen übertrieben Statementoder Connectionwerde sie schließen.

Enerccio
quelle
1

Ich habe die folgende Methode erstellt, um wiederverwendbaren One Liner zu erstellen:

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

Ich verwende diesen Code in einer übergeordneten Klasse, die an alle meine Klassen geerbt wurde, die DB-Abfragen senden. Ich kann den Oneliner für alle Abfragen verwenden, auch wenn ich kein resultSet habe. Die Methode sorgt dafür, dass ResultSet, Statement, Connection in der richtigen Reihenfolge geschlossen werden. So sieht mein Endblock aus.

finally {
    oneMethodToCloseThemAll(resultSet, preStatement, sqlConnection);
}
springe
quelle
-1

Soweit ich mich erinnere, implementieren Resultsets und Anweisungen im aktuellen JDBC die AutoCloseable-Schnittstelle. Das heißt, sie werden automatisch geschlossen, wenn sie zerstört werden oder den Geltungsbereich verlassen.

Mad Calm
quelle
3
Nein, das bedeutet nur, dass das closeam Ende einer Try-with-Resources-Anweisung aufgerufen wird. Siehe docs.oracle.com/javase/tutorial/essential/exceptions/… und docs.oracle.com/javase/8/docs/api/java/lang/AutoCloseable.html .
Zeemee
-1

Einige Komfortfunktionen:

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
quelle
Hier schließt nichts etwas. Nur eine sinnlose Schleife, die die gesamte Antwort verschwenderisch liest, obwohl sie eindeutig nicht mehr gewünscht wird.
Marquis von Lorne
-1

Mit Java 6 Form ist es meiner Meinung nach besser zu überprüfen, ob es geschlossen ist oder nicht, bevor es geschlossen wird (zum Beispiel, wenn ein Verbindungspooler die Verbindung in einem anderen Thread entfernt) - zum Beispiel ein Netzwerkproblem -, dass die Anweisung und der Ergebnismengenstatus geschlossen werden können. (Es kommt nicht oft vor, aber ich hatte dieses Problem mit Oracle und DBCP). Mein Muster dafür ist (in älterer Java-Syntax):

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

Theoretisch ist es nicht 100% perfekt, da zwischen der Überprüfung des Schließzustands und dem Schließen selbst ein kleiner Raum für die Zustandsänderung besteht. Im schlimmsten Fall erhalten Sie lange eine Warnung. - aber es ist weniger als die Möglichkeit einer Statusänderung bei langfristigen Abfragen. Wir verwenden dieses Muster in der Produktion mit einer "durchschnittlichen" Last (150 gleichzeitige Benutzer) und hatten kein Problem damit - sehen Sie diese Warnmeldung also nie.

Csákány Róbert
quelle
Sie brauchen die isClosed()Tests nicht, da das Schließen von bereits geschlossenen Tests ein No-Op ist. Dadurch wird das Problem mit dem Zeitfenster beseitigt. Was auch durch die Erstellung der Connection, Statementund ResultSetlokalen Variablen beseitigt würde .
Marquis von Lorne