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