Spring 事务:意外的回滚行为
Spring transaction: unexpected rollback behavior
我正在做一个简单的调试实验。
首先我将几条记录插入数据库,然后我进行无效数据转换,这将抛出 DataIntegrityViolationException,但我会捕获异常。
我预计记录会成功插入数据库,因为我捕获了已检查的异常。但是整个事情都回滚了。
我再次使用 TransactionTemplate 而不是使用注释进行实验,结果相同。
我的问题是:
- 这是预期的行为吗?
- 如果1号的答案是yes,那么我就捕获了异常,spring怎么可能知道抛出异常?
这是我的代码:
public void insertValue() {
jdbcTemplate.execute("insert into people (person_id, name) values (4, 'asjkdhadsjkqhweqkewhkashdkahd')");
jdbcTemplate.execute("insert into people (person_id, name) values (5, 'tttqqq')");
}
// this should throw exception
public void truncateValue() {
jdbcTemplate.execute("alter table people alter column name varchar(7)");
}
public void jdbc_calls() {
insertValue();
try {
truncateValue();
} catch (Exception e) {
System.out.println(e.getMessage());
}
System.out.println("Finish");
}
public void run() {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
transactionTemplate.execute(transactionStatus -> {
try {
jdbc_calls();
} catch (RuntimeException e) {
throw e;
} catch (Throwable e) {
throw new RuntimeException(e);
}
return null;
});
}
关于问题 2 的更多信息。
这是 TransactionTemplate.execute() 的源代码
据我了解,如果我不抛出异常,则不会触发 rollbackOnException。
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
}
else {
TransactionStatus status = this.transactionManager.getTransaction(this);
T result;
try {
result = action.doInTransaction(status);
}
catch (RuntimeException | Error ex) {
// Transactional code threw application exception -> rollback
rollbackOnException(status, ex);
throw ex;
}
catch (Throwable ex) {
// Transactional code threw unexpected exception -> rollback
rollbackOnException(status, ex);
throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
}
this.transactionManager.commit(status);
return result;
}
}
- is this the expected behavior?
是的,是的。
- If anwser to No.1 is yes, then I catch the exception, how is it possible that spring knows an exception is thrown?
发生异常时,spring会将您的交易标记为rollbackOnly
。
因此,即使您捕获到异常,在您的方法结束时,您的事务仍会回滚。
对于你的情况,我不明白你为什么使用 @Transaction
,因为无论是否发生异常你都想提交。
编辑
当您使用 DB 事务时,事务调用委托给 EntityManager。
看看AbstractEntityManagerImpl#handlePersistenceException
:
@Override
public void handlePersistenceException(PersistenceException e) {
if ( e instanceof NoResultException ) {
return;
}
if ( e instanceof NonUniqueResultException ) {
return;
}
if ( e instanceof LockTimeoutException ) {
return;
}
if ( e instanceof QueryTimeoutException ) {
return;
}
try {
markForRollbackOnly();
}
catch ( Exception ne ) {
//we do not want the subsequent exception to swallow the original one
LOG.unableToMarkForRollbackOnPersistenceException(ne);
}
}
发生异常时,EntityManager 将您的事务标记为 rollbackOnly
,然后抛出异常供您捕获。
在您的服务中捕获到异常后,AbstractPlatformTransactionManager
将尝试提交(因为如您所知,那里没有检测到异常),但 EntityManager 拒绝提交,因为它检测到事务标记为仅回滚。
如果您阅读异常,您会看到类似以下内容:
javax.persistence.RollbackException: Transaction marked as rollbackOnly
我正在做一个简单的调试实验。
首先我将几条记录插入数据库,然后我进行无效数据转换,这将抛出 DataIntegrityViolationException,但我会捕获异常。
我预计记录会成功插入数据库,因为我捕获了已检查的异常。但是整个事情都回滚了。
我再次使用 TransactionTemplate 而不是使用注释进行实验,结果相同。
我的问题是:
- 这是预期的行为吗?
- 如果1号的答案是yes,那么我就捕获了异常,spring怎么可能知道抛出异常?
这是我的代码:
public void insertValue() {
jdbcTemplate.execute("insert into people (person_id, name) values (4, 'asjkdhadsjkqhweqkewhkashdkahd')");
jdbcTemplate.execute("insert into people (person_id, name) values (5, 'tttqqq')");
}
// this should throw exception
public void truncateValue() {
jdbcTemplate.execute("alter table people alter column name varchar(7)");
}
public void jdbc_calls() {
insertValue();
try {
truncateValue();
} catch (Exception e) {
System.out.println(e.getMessage());
}
System.out.println("Finish");
}
public void run() {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
transactionTemplate.execute(transactionStatus -> {
try {
jdbc_calls();
} catch (RuntimeException e) {
throw e;
} catch (Throwable e) {
throw new RuntimeException(e);
}
return null;
});
}
关于问题 2 的更多信息。 这是 TransactionTemplate.execute() 的源代码 据我了解,如果我不抛出异常,则不会触发 rollbackOnException。
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
}
else {
TransactionStatus status = this.transactionManager.getTransaction(this);
T result;
try {
result = action.doInTransaction(status);
}
catch (RuntimeException | Error ex) {
// Transactional code threw application exception -> rollback
rollbackOnException(status, ex);
throw ex;
}
catch (Throwable ex) {
// Transactional code threw unexpected exception -> rollback
rollbackOnException(status, ex);
throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
}
this.transactionManager.commit(status);
return result;
}
}
- is this the expected behavior?
是的,是的。
- If anwser to No.1 is yes, then I catch the exception, how is it possible that spring knows an exception is thrown?
发生异常时,spring会将您的交易标记为rollbackOnly
。
因此,即使您捕获到异常,在您的方法结束时,您的事务仍会回滚。
对于你的情况,我不明白你为什么使用 @Transaction
,因为无论是否发生异常你都想提交。
编辑
当您使用 DB 事务时,事务调用委托给 EntityManager。
看看AbstractEntityManagerImpl#handlePersistenceException
:
@Override
public void handlePersistenceException(PersistenceException e) {
if ( e instanceof NoResultException ) {
return;
}
if ( e instanceof NonUniqueResultException ) {
return;
}
if ( e instanceof LockTimeoutException ) {
return;
}
if ( e instanceof QueryTimeoutException ) {
return;
}
try {
markForRollbackOnly();
}
catch ( Exception ne ) {
//we do not want the subsequent exception to swallow the original one
LOG.unableToMarkForRollbackOnPersistenceException(ne);
}
}
发生异常时,EntityManager 将您的事务标记为 rollbackOnly
,然后抛出异常供您捕获。
在您的服务中捕获到异常后,AbstractPlatformTransactionManager
将尝试提交(因为如您所知,那里没有检测到异常),但 EntityManager 拒绝提交,因为它检测到事务标记为仅回滚。
如果您阅读异常,您会看到类似以下内容:
javax.persistence.RollbackException: Transaction marked as rollbackOnly