Spring事务默认传播行为PROPAGATION_REQUIRED:怎样做到异常被捕获处理后事务还会回滚
Spring事务默认传播行为PROPAGATION_REQUIRED:怎样做到异常被捕获处理后事务还会回滚
前言
PROPAGATION_REQUIRED是Spring默认的事务传播机制,如果当前没有事务,就新建一个事务,如果当前已经存在一个事务,加入到当前事务。
话不多说,直接上代码。
场景代码:在方法test
、insert(User user)
和insert_Exception(User user)
方法上都添加了@Transactional
,事务传播行为都是默认的PROPAGATION_REQUIRED
。
在测试方法中事务默认会自动回滚,可以在测试方法上加
@Rollback(value = false)
修改配置
@Transactional
@Rollback(value = false)
@Test
public void test(){
...
// 插入一条记录,正常执行
userService.insert(user1);
...
try {
// 插入一条记录,执行过程中抛出一个RuntimeException
userService.insert_Exception(user2);
} catch (Exception e) {
log.error("异常信息: {}", e.getMessage());
}
}
public class UserService {
...
@Transactional
void insert(User user) {
userMapper.insert(user);
}
@Transactional
void insert_Exception(User user) {
userMapper.insert(user);
throw new RuntimeException("抛出异常");
}
}
test
执行结果:事务回滚,插入记录均不成功。
相信结论大家早已知晓。
userService.insert(user1)
执行正常,userService.insert_Exception(user2)
执行过程中抛出异常,异常最终在test
方法中被捕获,test
方法执行正常,那么事务是怎么知道要回滚的?
解析
我们已经在测试方法上加了@Transactional
注解,测试方法会被配置为在事务中运行,所以会执行startTransaction
。
有一个属性:
flaggedForRollback
。flaggedForRollback
是事务的回滚标志,根据这个回滚标志,会立即强制提交或回滚配置测试上下文的事务。
// 为配置的测试上下文启动一个新的事务
void startTransaction() {
...
// 已经加了@Rollback(value = false),事务不会自动回滚,值为false
this.flaggedForRollback = this.defaultRollback;
// 通过事务管理器获取当前存在的事务状态
this.transactionStatus = this.transactionManager.getTransaction(this.transactionDefinition);
...
}
通过事务管理器获取当前的事务状态transactionStatus
,首先创建一个事务对象,通过TransactionSynchronizationManager获取connectionHolder,由于当前不存在事务,所以不存在connectionHolder
。
connectionHolder:包装JDBC连接的资源持有者,而spring的事务是通过数据库连接来实现的,所以开启一个新事务时会创建一个数据源对象来表示ConnectionHolder,作为DataSourceTransactionManager的事务对象使用。
// 根据事务对象中的connectionHolder来判断当前是否存在事务
protected boolean isExistingTransaction(Object transaction) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
// 事务对象中connectionHolder为空,返回false
return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive());
}
因为当前不存在事务,isExistingTransaction(Object transaction)
执行结果返回false,且要开启的事务传播行为是PROPAGATION_REQUIRED
,最后会执行startTransaction
方法,开启新事务,标记当前事务状态transactionStatus
的newTransaction
和newConnectionHolder
为true,表示当前是一个新事务,同时也会创建一个connectionHolder
对象,该对象中包含了数据源的连接。
事务开启后,代码执行到userService.insert(user1)
,首先获取当前的事务状态,执行doGetTransaction()
获取当前事务状态的事务对象,由于当前已经存在事务,当前事务状态的事务对象中connectionHolder不为空,执行isExistingTransaction(Object transaction)
返回true,进入handleExistingTransaction(def, transaction, debugEnabled)
方法,且userService.insert(user1)
的事务传播行为是PROPAGATION_REQUIRED
,此时不会开启新事务,只是创建一个TransactionStatus
来表示其当前的事务状态,其中newTransaction
和newConnectionHolder
为false,事务状态中的connectionHolder
与开启事务时创建的connectionHolder
是同一个对象。
userService.insert(user1)
正常执行,然后进入commitTransactionAfterReturning(txInfo)
。
/**
* 在成功完成调用后执行
* txInfo -关于当前事务的信息
*/
protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
...
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
}
在commit
方法中,userService.insert(user1)
已经加入外围方法的事务,根据其事务状态会执行processCommit(defStatus)
方法,这个方法会处理实际提交的事务,这里事务没有实际提交,所以并没有执行任何操作。
以下是执行commit
对事务状态的一些判断
// status 当前事务状态
public final void commit(TransactionStatus status) throws TransactionException {
// 判断当前事务是否完成,就是说是否已经提交或者回滚
if (status.isCompleted()) {
throw new IllegalTransactionStateException(
"Transaction is already completed - do not call commit or rollback more than once per transaction");
}
DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
// 如果回滚标志已经设置为true,则开始回滚
if (defStatus.isLocalRollbackOnly()) {
...
processRollback(defStatus, false);
return;
}
// shouldCommitOnGlobalRollbackOnly():返回是否以全局方式对已标记为仅回滚的事务调用doCommit,默认值:false。如果 // rollbackOnly标记被设置为true,那么事务就会回滚而不会提交
// defStatus.isGlobalRollbackOnly(),判断rollbackOnly的值
if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
...
}
// 处理回滚操作
processRollback(defStatus, true);
return;
}
// 提交事务
processCommit(defStatus);
}
继续执行到userService.insert_Exception(user2)
,这里也会加入外围方法的事务,创建一个事务状态对象,操作和userService.insert(user1)
一样,但是在方法执行过程中抛出异常,这里不会执行commitTransactionAfterReturning
,变成执行completeTransactionAfterThrowing(txInfo, ex)
。
开始处理回滚操作,执行doSetRollbackOnly
方法,将ConnectionHolder的仅回滚标记rollbackOnly
设置为为true
,最后userService.insert_Exception(user2)
抛出异常到外围方法,异常在外围方法中被捕获并处理。
这时因为已经处理了userService.insert_Exception(user2)
抛出的异常,测试方法test是正常执行完成的,而且之前添加了@Rollback(value = false)
,将flaggedForRollback设置为false,事务并不会自动回滚,所以事务开始执行提交操作,但是在处理事务提交之前,会根据事务状态(status
中的rollbackOnly
)和全局事务的仅回滚标记(connectionHolder
中的rollbackOnly
)分别进行判断是否要进行回滚操作。
如果
this.flaggedForRollback
的值为true,那么在endTransaction()
会直接执行rollback
方法,不会执行commit
方法,在rollback
方法中会执行processRollback(defStatus, false)
,然后完成事务回滚。
// 根据回滚标志,立即强制提交或回滚配置测试上下文的事务
void endTransaction() {
...
try {
// 由于之前添加了@Rollback(value = false),将flaggedForRollback设置为false,这里会执行提交方法
if (this.flaggedForRollback) {
this.transactionManager.rollback(this.transactionStatus);
}
else {
this.transactionManager.commit(this.transactionStatus);
}
}
...
}
// status 当前事务状态
public final void commit(TransactionStatus status) throws TransactionException {
...
DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
// 通过事务状态的仅回滚标志来判断是否回滚
if (defStatus.isLocalRollbackOnly()) {
...
// 处理回滚操作,参数unexpected为false
processRollback(defStatus, false);
return;
}
// 通过判断全局事务本身被事务协调器是否被标记为回滚
if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
...通过检查事务对象确定仅回滚标志
// 处理回滚操作,参数unexpected为true
processRollback(defStatus, true);
return;
}
// 提交事务
processCommit(defStatus);
}
在上面的代码中,有两处代码会判断仅回滚标志rollbackOnly
,但是最终都会执行processRollback
来回滚事务,那么processRollback(defStatus, false)
和processRollback(defStatus, true)
会执行哪个方法,为什么它们的第二个参数(参数名:unexpected)又有什么不同呢?
首先解释一下为什么仅回滚标志rollbackOnly
能决定现有事务的回滚:
在事务管理器中,有一个属性globalRollbackOnParticipationFailure
,默认值为true,用来设置是否在参与的事务失败后将现有事务全局标记为仅回滚。而在shouldCommitOnGlobalRollbackOnly()
方法中,决定了是否以全局方式对已标记为仅回滚的事务调用doCommit
,也就是执行提交操作,其默认值为false。
因此,在这种情况下(设置了仅回滚标志为true),事务管理器将触发回滚,然后抛出一个unexpectedRollbackexception
。
那么以上代码会执行哪个方法?
defStatus
指的是test方法的事务状态,defStatus.isLocalRollbackOnly()
检查的是defStatus
的仅回滚标记rollbackOnly
,test方法正常执行成功,事务状态中的仅回滚标记为false。
想一想在方法执行失败时是怎么设置仅回滚标记rollbackOnly
的,没错,就是在connectionHolder
,所以检查全局事务是否被标记为回滚就是检查connectionHolder
中的仅回滚标记rollbackOnly
。
// defStatus.isGlobalRollbackOnly()的实现
@Override
public boolean isRollbackOnly() {
return getConnectionHolder().isRollbackOnly();
}
为什么processRollback
处理回滚操作,参数unexpected一个为true,一个为false?
unexpected
,就是意外的意思。
processRollback(defStatus, true)
:test方法正常执行完成,本来要执行提交操作,但是全局事务本身被事务协调器被标记为回滚,所以变为执行回滚操作,unexpected
为true表示这是意外回滚。简而言之,本来要提交,最后回滚了,这就是意外回滚,所以最后也会抛出意外回滚异常。processRollback(defStatus, false)
:方法执行过程中抛有异常未处理导致事务回滚时,unexpected
为false,unexpected
为为false,表示该回滚并不意外。
总结
一言以蔽之:在事务传播行为都是PROPAGATION_REQUIRED
的情况下,如果方法开启了新事务,首先会获取当前事务状态,没有connectionHolder
就会创建一个,如果方法内部调用事务方法,被调用的方法都会加入当前事务,共用同一个connectionHolder
,如果被调用的方法出现异常而导致回滚,就会设置connectionHolder
中的仅回滚标记rollbackOnly
为真,就是标记全局事务为仅回滚,这样就算在外围方法中处理了被调用方法抛出的异常,最终事务还是会执行回滚操作。
OK,就这样吧,有错误的地方可以指出,欢迎讨论!
转载自:https://juejin.cn/post/7227107202724266021