Откат транзакции в SQLException с использованием нового блока try-with-resources
У меня проблема с try-with-ресурсами, и я прошу только быть уверенным. Могу ли я использовать его, если мне нужно реагировать на исключение, и мне все еще нужен ресурс в блоке catch? Приведенный ниже пример:
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
}
Я боюсь, что я все еще обречен использовать старый try-catch - наконец, в этом случае, даже в соответствии с документацией oracle - "catch и, наконец, блокирует оператор try-with-resources, выполняется любой блок catch или finally после того, как объявленные ресурсы были закрыты".
Ответы
Ответ 1
В соответствии с спецификацией языка соединение будет закрыто до того, как будет выполняться предложение 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.
Ответ 2
В вашем коде вы ловите "SQLException" для выполнения autoCommit reset. Любое исключение из среды выполнения (например, исключение нулевого указателя) будет выходить из вашего кода без сброса автоматической фиксации.
Синтаксис try-with-resource заставляет компилятор генерировать замечательный код для охвата всех путей выполнения и следить за всеми подавленными исключениями через закрытие. С помощью нескольких вспомогательных классов вы можете вставлять commit/rollback и reset -auto-commit в процесс генерации кода:
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);
}
}
Теперь вы можете управлять откатом и autocommit с синтаксисом "попробуйте с ресурсом" следующим образом:
try(Connection conn = getConnection(),
AutoSetAutoCommit a = new AutoSetAutoCommit(conn,false),
AutoRollback tm = new AutoRollback(conn))
{
// Do stuff
tm.commit();
}
Ответ 3
//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
}
}
Ответ 4
В приведенном выше примере я думаю, что лучше разместить con.commit()
внутри вложенных try-catch
, потому что он также может бросать 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);
}
}
У нас была такая проблема в нашей производственной среде с незакрытыми сеансами.