Transaktions-Rollback bei SQLException mit neuem try-with-resources-Block

Lesezeit: 7 Minuten

Ich habe ein Problem mit Try-with-Ressourcen und frage nur um sicherzugehen. Kann ich es verwenden, wenn ich auf eine Ausnahme reagieren muss und trotzdem die Ressource im Catch-Block benötige? Beispiel gegeben ist dies:

try (java.sql.Connection con = createConnection())
{
    con.setAutoCommit(false);
    Statement stm = con.createStatement();
    stm.execute(someQuery); // causes SQLException
}
catch(SQLException ex)
{
    con.rollback();
    // do other stuff
}

Ich befürchte, dass ich in diesem Fall immer noch dazu verdammt bin, den alten try-catch-finally zu verwenden, selbst laut Oracle-Dokumentation – “catch and finally blockiert in einer try-with-resources-Anweisung, jeder catch- oder finally-Block wird nach den Ressourcen ausgeführt für geschlossen erklärt.”

  • Wenn in diesem Fall die Verbindung selbst fehlgeschlagen ist, macht es keinen Sinn, sie zurückzusetzen. Der Umfang der con ist auf den Versuchsblock beschränkt.

    – bprasanna

    2. April 2013 um 11:55 Uhr


  • Auch diese Frage kann helfen. stackoverflow.com/questions/9260159/…

    – Kevin S

    23. Oktober 2013 um 19:42 Uhr

  • Von allen interessanten Optionen bevorzuge ich immer noch das Original try-catch-finally

    – Adam

    12. Oktober 2017 um 14:27 Uhr

Alfs Benutzeravatar
Alf

Gemäß der Sprachspezifikation wird die Verbindung geschlossen, bevor die catch-Klausel ausgeführt wird (http://docs.oracle.com/javase/specs/jls/se7/html/jls-14.html#jls-14.20.3.2).

Eine mögliche Lösung besteht darin, try-with-resources-Anweisungen zu verschachteln:

try (java.sql.Connection con = createConnection())
{
    con.setAutoCommit(false);
    try (Statement stm = con.createStatement())
    {
        stm.execute(someQuery); // causes SQLException
    }
    catch(SQLException ex)
    {
        con.rollback();
        con.setAutoCommit(true);
        throw ex;
    }
    con.commit();
    con.setAutoCommit(true);
}

Hoffentlich verdeutlicht das den Punkt. Dies sollte ziemlich verbessert werden, wenn Sie es im Produktionscode verwenden möchten.

Wenn Sie beispielsweise einen Verbindungspool verwenden, müssen Sie die Verbindung so zurückgeben, wie Sie sie erhalten haben, also con.setAutoCommit(true); sollte in einer finally-Klausel erfolgen. Dies würde bedeuten, dass das äußere Try-with-Ressourcen ein traditionelles Try-Catch-Finally sein sollte.

Bearbeiten (2018)

Ich sehe immer noch Leute, die dies kommentieren, also dachte ich, ich würde es 2018 beantworten. Ich arbeite nicht mehr in Java, hauptsächlich in Scala, Clojure und Kotlin, und dieser Code wurde nicht getestet, also behandeln Sie dies bitte nur als ein weiteres Beispiel. Da Java jedoch Lambdas hat, denke ich, dass der folgende Ansatz viel besser ist. Und ich habe ähnliche Dinge im Produktionscode in diesen anderen Sprachen gemacht.

Bei diesem Ansatz gibt es eine inTransaction-Funktion, die all das unangenehme Transaktionszeug behandelt. Aber die Verwendung ist ziemlich einfach.

public class Foo {

    interface ConnectionProvider {
        Connection get() throws SQLException;
    }

    public static <A> A doInTransation(ConnectionProvider connectionProvider, Function<Connection, A> f) throws SQLException {
        Connection connection = null;
        A returnValue;
        boolean initialAutocommit = false;
        try {
            connection = connectionProvider.get();
            initialAutocommit = connection.getAutoCommit();
            connection.setAutoCommit(false);
            returnValue = f.apply(connection);
            connection.commit();
            return returnValue;
        } catch (Throwable throwable) {
            // You may not want to handle all throwables, but you should with most, e.g.
            // Scala has examples: https://github.com/scala/scala/blob/v2.9.3/src/library/scala/util/control/NonFatal.scala#L1
            if (connection != null) {
                connection.rollback();
            }
            throw throwable;
        } finally {
            if (connection != null) {
                try {
                    if(initialAutocommit){
                        connection.setAutoCommit(true);
                    }
                    connection.close();
                } catch (Throwable e) {
                    // Use your own logger here. And again, maybe not catch throwable,
                    // but then again, you should never throw from a finally ;)
                    StringWriter out = new StringWriter();
                    e.printStackTrace(new PrintWriter(out));
                    System.err.println("Could not close connection " + out.toString());
                }
            }
        }
    }

    public static void main(String[] args) throws SQLException {
        DataSource ds = null;

        // Usage example:
        doInTransation(ds::getConnection, (Connection c) -> {
            // Do whatever you want in a transaction
            return 1;
        });
    }
}

Ich würde hoffen, dass es da draußen einige kampferprobte Bibliotheken gibt, die diese Dinge für Sie erledigen, zumindest in diesen anderen Sprachen.

Ich sehe, dass es mehrere Kommentare zu Autocommit und Verbindungspools gibt. Die obigen Beispiele sollten unabhängig davon sein, woher die Verbindung kam, ein Pool oder nicht, dh sie sollten nur dann auf wahr zurückgesetzt werden, wenn dies der ursprüngliche Wert war. Wenn es also aus einem Pool falsch ist, sollte es nicht berührt werden.

Ein letztes Wort zu Try-with-Ressourcen. Ich denke nicht, dass es eine sehr gute Abstraktion ist, also würde ich vorsichtig sein, es in komplexeren Szenarien zu verwenden.

  • Sei dir dieser Berufung bewusst setAutoCommit ruft ein COMMIT für jede anstehende Transaktion auf. Seien Sie also vorsichtig bei komplizierterem Code.

    – Basilikum Bourque

    13. August 2015 um 22:43 Uhr

  • “Dies würde bedeuten, dass das äußere Try-with-Ressourcen ein traditionelles Try-Catch-Finally sein sollte.” – also ist das Muster “mit Ressourcen versuchen” für JDBC-Verbindungen mit Verbindungspools tatsächlich völlig nutzlos …?

    – Itai

    7. März 2016 um 19:05 Uhr

  • “Wenn Sie beispielsweise einen Verbindungspool verwenden, müssen Sie die Verbindung so zurückgeben, wie Sie sie erhalten haben, also sollte con.setAutoCommit(true); in einer finally-Klausel ausgeführt werden.” — Wenn sich der Verbindungspool an die JDBC-Spezifikation hält, ist das nicht wahr. Gemäß der Spezifikation muss für eine neu ausgecheckte Verbindung immer Autocommit aktiviert sein. Die meisten Verbindungspools wie c3p0 stellen dies sicher, selbst wenn die Verbindung mit deaktiviertem Autocommit eingecheckt wurde.

    – Jackson

    10. April 2017 um 17:41 Uhr


  • @Alf in der letzten Zeile, warum müssen Sie das Autocommit auf “true” setzen, wenn Sie gerade ein Commit ausgeführt haben und die Verbindung sofort danach geschlossen wird?

    – Luigi Cortese

    9. Juni 2017 um 9:24 Uhr

  • Aber man fängt nur SQLException. Was ist, wenn eine nicht überprüfte Ausnahme, wie z NullPointerException tritt ein? Wird Ihre Transaktion nie rückgängig gemacht?

    – Garret Wilson

    21. Mai 2018 um 17:12 Uhr

Benutzeravatar von ChrisCantrell
ChrisCantrell

In Ihrem Code fangen Sie “SQLException” ab, um den AutoCommit-Reset durchzuführen. Jede Art von Laufzeitausnahme (z. B. eine Nullzeiger-Ausnahme) sprudelt aus Ihrem Code, ohne dass der Autocommit zurückgesetzt wird.

Die Try-with-Resource-Syntax veranlasst den Compiler, einen wunderbaren Code zu generieren, um alle Ausführungspfade abzudecken und mit allen unterdrückten Ausnahmen durch die Closings Schritt zu halten. Mit ein paar Hilfsklassen können Sie Commit/Rollback und Reset-Auto-Commit in den Codegenerierungsprozess einfügen:

import java.sql.SQLException;
import java.sql.Connection;

public class AutoRollback implements AutoCloseable {

    private Connection conn;
    private boolean committed;

    public AutoRollback(Connection conn) throws SQLException {
        this.conn = conn;        
    }

    public void commit() throws SQLException {
        conn.commit();
        committed = true;
    }

    @Override
    public void close() throws SQLException {
        if(!committed) {
            conn.rollback();
        }
    }

}

public class AutoSetAutoCommit implements AutoCloseable {

    private Connection conn;
    private boolean originalAutoCommit;

    public AutoSetAutoCommit(Connection conn, boolean autoCommit) throws SQLException {
        this.conn = conn;
        originalAutoCommit = conn.getAutoCommit();
        conn.setAutoCommit(autoCommit);
    }

    @Override
    public void close() throws SQLException {
        conn.setAutoCommit(originalAutoCommit);
    }

}

Jetzt können Sie Rollback und Autocommit mit der Syntax “try with resource” wie folgt steuern:

    try(Connection conn = getConnection(),
        AutoSetAutoCommit a = new AutoSetAutoCommit(conn,false),
        AutoRollback tm = new AutoRollback(conn)) 
    {

        // Do stuff

        tm.commit();
    } 

  • Interessanter Code, ich habe mir nicht die Zeit genommen, die AutoCloseable-Schnittstelle auszuprobieren. Dies scheint ein guter Ansatz zu sein, um die finally-Klausel zu ersetzen. In der Produktion wäre der Code mit nur einer try-with-Ressource viel einfacher. Nett !

    – AxelH

    18. Mai 2016 um 10:58 Uhr

  • 1. Warum deklarieren Sie in close()-Methoden nicht “throws SQLException”? Warum wickelst du nur als Ausnahme? 2. Warum erstellen Sie nicht alle Ressourcen in einem einzigen Try-Block? AFAIK würden sie in umgekehrter Reihenfolge geschlossen, Sie haben sie deklariert.

    – Sabine

    28. Januar 2017 um 10:48 Uhr


  • Sehr gute Punkte! Ich werde die Schließungen ändern, um SQL-Ausnahmen auszulösen, und sie in einem Versuch kombinieren, wie Sie und AxelH vorgeschlagen haben. Danke für die Rückmeldung.

    – ChrisCantrell

    30. Januar 2017 um 14:06 Uhr

  • @Chris – Entschuldigung, ich habe Ihre Antwort falsch interpretiert. Ich dachte, Ihr Fokus lag in meinem Beispiel auf der fehlenden Ausnahmebehandlung, aber ich sehe jetzt, dass es sich um Autocommit handelte. Wie auch immer, die ursprüngliche Frage hatte nichts mit der Ausnahmebehandlung zu tun, sondern lediglich den Umfang der Ressourcen, die im Header des try-Blocks definiert sind

    – Jan Hruby

    26. Februar 2019 um 16:40 Uhr


  • Die “,”s in der Try-Ressource müssen durch “;” ersetzt werden. Meiner will das zumindest.

    – cs94njw

    11. März 2019 um 15:43 Uhr

    //try with resources
    try(Connection conn = this.connectionProvider.getConnection()){//auto close BEFORE reach this , catch block, so we need a inner try block for statement
        boolean oldAutoCommit=conn.getAutoCommit();
        conn.setAutoCommit(false);//auto commit to false
        try(
            Statement stm = con.createStatement()
        ){
            stm.execute(someQuery); // causes SQLException
            conn.commit();//commit
        }
        catch (SQLException ex){
            conn.rollback();//error, rollback
            throw ex;//If you need to throw the exception to the caller
        }
        finally {
            conn.setAutoCommit(oldAutoCommit);//reset auto commit
        }
    }

Benutzeravatar von shambalaxx
shambalaxx

Im obigen Beispiel denke ich, dass es besser ist, zu setzen con.commit() innen verschachtelt try-catch weil es auch werfen kann SQLException.

 try (java.sql.Connection con = createConnection())
    {
        con.setAutoCommit(false);
        try (Statement stm = con.createStatement())
        {
            stm.execute(someQuery); // causes SQLException
            con.commit();           // also causes SQLException!
        }
        catch(SQLException ex)
        {
            con.rollback();
            throw ex;
        }finally{
            con.setAutoCommit(true);
        }
    }

Wir hatten ein solches Problem in unserer Produktionsumgebung mit nicht geschlossenen Sitzungen.

1435910cookie-checkTransaktions-Rollback bei SQLException mit neuem try-with-resources-Block

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy