Mehrfaches Wiederverwenden eines PreparedStatements


96

Kann ich bei Verwendung von PreparedStatement mit einer einzelnen gemeinsamen Verbindung ohne Pool eine Instanz für jede dml / sql-Operation neu erstellen, die die Leistung vorbereiteter Anweisungen beibehält?

Ich meine:

for (int i=0; i<1000; i++) {
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    preparedStatement.setObject(1, someValue);
    preparedStatement.executeQuery();
    preparedStatement.close();
}

anstatt:

PreparedStatement preparedStatement = connection.prepareStatement(sql);
for (int i=0; i<1000; i++) {
    preparedStatement.clearParameters();
    preparedStatement.setObject(1, someValue);
    preparedStatement.executeQuery();
}
preparedStatement.close();

Meine Frage ergibt sich aus der Tatsache, dass ich diesen Code in eine Multithread-Umgebung stellen möchte. Können Sie mir einen Rat geben? Vielen Dank


Also sqländert sich Ihre Anfrage nicht mit in der Schleife? Wenn sich diese Abfrage nicht für jede Iteration der Schleife ändert, warum erstellen Sie dann PreparedStatementfür jede Iteration eine neue (im ersten Codeausschnitt)? Gibt es einen Grund dafür?
Sabir Khan

Nehmen wir an, wenn sich die Abfrage ändert, ist der zweite Ansatz immer noch besser, oder? Irgendwelche Nachteile?
Stunner

Antworten:


144

Der zweite Weg ist ein bisschen effizienter, aber ein viel besserer Weg ist, sie in Stapeln auszuführen:

public void executeBatch(List<Entity> entities) throws SQLException { 
    try (
        Connection connection = dataSource.getConnection();
        PreparedStatement statement = connection.prepareStatement(SQL);
    ) {
        for (Entity entity : entities) {
            statement.setObject(1, entity.getSomeProperty());
            // ...

            statement.addBatch();
        }

        statement.executeBatch();
    }
}

Sie sind jedoch abhängig von der Implementierung des JDBC-Treibers, wie viele Stapel Sie gleichzeitig ausführen können. Sie können sie beispielsweise alle 1000 Stapel ausführen:

public void executeBatch(List<Entity> entities) throws SQLException { 
    try (
        Connection connection = dataSource.getConnection();
        PreparedStatement statement = connection.prepareStatement(SQL);
    ) {
        int i = 0;

        for (Entity entity : entities) {
            statement.setObject(1, entity.getSomeProperty());
            // ...

            statement.addBatch();
            i++;

            if (i % 1000 == 0 || i == entities.size()) {
                statement.executeBatch(); // Execute every 1000 items.
            }
        }
    }
}

In Bezug auf Multithread-Umgebungen müssen Sie sich darüber keine Gedanken machen, wenn Sie die Verbindung und die Anweisung im kürzestmöglichen Umfang innerhalb desselben Methodenblocks gemäß der normalen JDBC-Redewendung mithilfe der Anweisung try-with-resources wie in Abbildung gezeigt erwerben und schließen über Schnipsel.

Wenn diese Stapel transaktional sind, möchten Sie die automatische Festschreibung der Verbindung deaktivieren und die Transaktion erst festschreiben, wenn alle Stapel abgeschlossen sind. Andernfalls kann es zu einer fehlerhaften Datenbank kommen, wenn die ersten Stapel erfolgreich waren und die späteren nicht.

public void executeBatch(List<Entity> entities) throws SQLException { 
    try (Connection connection = dataSource.getConnection()) {
        connection.setAutoCommit(false);

        try (PreparedStatement statement = connection.prepareStatement(SQL)) {
            // ...

            try {
                connection.commit();
            } catch (SQLException e) {
                connection.rollback();
                throw e;
            }
        }
    }
}

inside the same method block- Sie meinen, dass jeder Thread seinen eigenen Stapel hat und diese Verbindung und Anweisungen von einer Seite im Stapel sind und von einer anderen Datenquelle für jeden neuen Aufruf von executeFunction (== jeder Thread) eine separate Verbindungsinstanz erhalten. Verstehe ich dich richtig? "
Pavel_K

Ich mache die erste Methode, aber während der Überwachung im SQL-Profiler werden mehrere vorbereitete Anweisungen statt einer wiederholt. Ich konnte nicht herausfinden, warum es mehrere Aussagen zeigt. Unterstützung erforderlich.
Schurkenjunge

Ihre Antwort ist gut, vorausgesetzt, die Abfrage ändert sich nicht in der Schleife. Was ist, wenn sich die Abfrage ändert, z. B. in meinem Fall, in dem die Abfrage geändert wird? Ich gehe davon aus, dass der zweite Ansatz immer noch besser ist. Bitte validieren
Stunner

13

Die Schleife in Ihrem Code ist nur ein stark vereinfachtes Beispiel, oder?

Es ist besser, das PreparedStatement nur einmal zu erstellen und es in der Schleife immer wieder zu verwenden.

In Situationen, in denen dies nicht möglich ist (weil es den Programmablauf zu sehr kompliziert hat), ist es immer noch vorteilhaft, ein PreparedStatement zu verwenden, auch wenn Sie es nur einmal verwenden, da die Serverseite der Arbeit (Parsen des SQL und Zwischenspeichern des Ausführungsplan), wird noch reduziert.

Um die Situation zu beheben, in der Sie das Java-seitige PreparedStatement wiederverwenden möchten, verfügen einige JDBC-Treiber (z. B. Oracle) über eine Caching-Funktion: Wenn Sie ein PreparedStatement für dasselbe SQL auf derselben Verbindung erstellen, erhalten Sie dasselbe (zwischengespeicherte) Instanz.

Informationen zu Multithreading: Ich glaube nicht, dass JDBC-Verbindungen über mehrere Threads hinweg gemeinsam genutzt werden können (dh von mehreren Threads gleichzeitig verwendet werden). Jeder Thread sollte seine eigene Verbindung aus dem Pool abrufen, verwenden und erneut in den Pool zurückgeben.


1
Tatsächlich hat die Verbindung ihren exklusiven Thread und jede Anweisung wird darin ausgeführt, aber ich greife über einen exponierten Stapel vorbereiteter Anweisungen auf diesen Thread zu. Andere gleichzeitige Threads übergeben zunächst nur die Parameter, die zum Erstellen aller vorbereiteten Anweisungen erforderlich sind. Anschließend können sie die Parameter gleichzeitig ändern
Steel Plume,
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.