2013-04-02 9 views
27

Ich habe ein Problem mit Try-mit-Ressourcen und ich frage nur um sicher zu sein. Kann ich es verwenden, wenn ich auf eine Ausnahme reagieren muss, und ich brauche immer noch die Ressource im Catch-Block? Beispiel angegeben:Transaktion Rollback auf SQLException mit neuen Try-mit-Ressourcen Block

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 fürchte, dass ich immer noch die alten try-catch-finally in diesem Fall, auch nach dem Oracle-Dokumentation zu verwenden, ist zum Scheitern verurteilt - „fangen und schließlich Blöcke in einer Try-mit-Ressourcen Anweisung wird jeder catch- oder finally-Block ausgeführt, nachdem die deklarierten Ressourcen geschlossen wurden. "

+2

In diesem Fall, wenn die Verbindung selbst fehlgeschlagen ist, hat es keinen Sinn, es zurückzurollen. Der Umfang von "con" ist beschränkt auf try block only. – learningloop

+0

Diese Frage kann auch helfen. http://stackoverflow.com/questions/9260159/java-7-automatic-resource-management-jdbc –

+0

Von allen interessanten Optionen gegeben, bevorzuge ich immer noch das Original "Try-Catch-finally" – Adam

Antwort

33

Je nach 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 ist zu nisten try-with-Ressourcen Aussagen:

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, die den Punkt zeigt. Dies sollte 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 die äußeren Try-with-resources eine traditionelle try-catch-finally sein sollten.

+0

nette Lösung, thx –

+1

Be Beachten Sie, dass das Aufrufen von [setAutoCommit] (http://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html#setAutoCommit-boolean-) ein COMMIT für eine ausstehende Transaktion aufruft. Sei also vorsichtig in komplizierterem Code. –

+1

"Dies würde bedeuten, dass die äußeren Versuch-mit-Ressourcen ein traditioneller Versuch sein sollten - Fang-endlich." - In der Tat ist das "try with resources" -Muster für JDBC-Verbindungen mit Verbindungspools völlig nutzlos ...? – Itai

3

In dem obigen Beispiel denke ich, es ist besser, con.commit() in verschachtelten try-catch setzen, weil es auch SQLException werfen kann.

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 solch ein Problem in unserer Produktionsumgebung mit nicht abgeschlossenen Sitzungen.

+2

Sie sollten eine finally-Anweisung verwenden, um den Autocommit festzulegen zurück zu true. Das würde verhindern, dass Sie diese Aussage zweimal haben. – AxelH

6
//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 
     } 
    } 
10

In Ihrem Code fangen Sie "SQLException" ab, um den AutoCommit-Reset durchzuführen. Jede Art von Laufzeitausnahme (wie eine Nullzeigerausnahme) wird aus Ihrem Code blasen, ohne die automatische Festschreibung zurückzusetzen.

Die Try-with-Resource-Syntax bewirkt, dass der Compiler einen wunderbaren Code generiert, der alle Ausführungspfade abdeckt und mit allen unterdrückten Ausnahmen durch die Closings Schritt hält. Mit ein paar Hilfsklassen können Sie einfügen Commit/Rollback und Reset-autocommit in den Codegenerierungsprozess:

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-Steuerung und Autocommit mit dem „versuchen, mit Ressource“ Syntax wie folgt:

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

     // Do stuff 

     tm.commit(); 
    } 
+1

Interessanter Code, ich habe nicht die Zeit genommen, AutoCloseable-Schnittstelle zu versuchen. Dies scheint eine gute Approche zu sein, die finally-Klausel zu ersetzen. In der Produktion wäre der Code viel einfacher mit nur einer Try-mit-Ressource.Nizza! – AxelH

+0

1. Warum deklarieren Sie nicht "wirft SQ LException "in close() Methoden? Warum wickelst du nur eine Ausnahme? 2. Warum erstellen Sie nicht alle Ressourcen in einem einzelnen try-Block? AFAIK würden sie in umgekehrter Reihenfolge geschlossen sein, du hast sie erklärt. – Sabine

+0

Sehr gute Punkte! Ich werde die Schlüsse ändern, um SQL-Ausnahmen zu werfen und sie in einem Versuch zu kombinieren, wie Sie und AxelH vorgeschlagen haben. Danke für die Rückmeldung. – ChrisCantrell