言語仕様によると、接続は catch 句が実行される前に閉じられます ( http://docs.oracle.com/javase/specs/jls/se7/html/jls-14.html#jls-14.20.3.2 ) .
考えられる解決策は、try-with-resources ステートメントをネストすることです。
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);
}
うまくいけば、それは要点を示しています。本番コードで使用する予定がある場合、これはかなり改善されるはずです。
たとえば、接続プールを使用している場合は、取得したとおりに接続を返す必要があるため、con.setAutoCommit(true); finally 句で実行する必要があります。これは、外側の try-with-resources が従来の try-catch-finally であることを意味します。
編集 (2018)
これについてまだコメントしている人がいるので、2018年に返信したいと思いました。主に Scala、Clojure、および Kotlin で作業しており、このコードはテストされていないため、これは別の例として扱ってください。ただし、Javaにはラムダがあるため、次のアプローチの方がはるかに優れていると思います。そして、これらの他の言語の製品コードで同様のことを行いました。
このアプローチには、すべての厄介なトランザクションを処理する inTransaction 関数があります。でも使い方は至ってシンプル。
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;
});
}
}
少なくともこれらの他の言語には、このようなことを行う戦闘テスト済みのライブラリがいくつかあることを願っています。
自動コミットと接続プールに関するコメントがいくつかあります。上記の例は、接続がどこから来たのか、プールであるかどうかにとらわれない必要があります。つまり、それが初期値である場合にのみ true に戻します。したがって、プールからの値が false の場合は、触れないでください。
リソースの試行に関する最後の言葉。これはあまり良い抽象化ではないと思うので、より複雑なシナリオで使用する場合は注意が必要です。