springboot事务回滚源码_Springboot源码分析之事务问题

这篇博客深入探讨了SpringBoot中事务回滚的源码实现,从@EnableTransactionManagement开始,介绍了TransactionInterceptor、TransactionAspectSupport等关键组件的工作流程。在事务处理中,通过检查TransactionAttribute和PlatformTransactionManager,决定是否进行回滚。文章还分析了异常处理和回滚逻辑,强调了仅支持运行时异常和错误机制的回滚。最后,通过案例分析和解决方案讨论了事务回滚可能遇到的问题。
摘要由CSDN通过智能技术生成

摘要:

事务在后端开发中无处不在,是数据一致性的最基本保证。要明白进事务的本质就是进到事务切面的代理方法中,最常见的是同一个类的非事务方法调用一个加了事务注解的方法没进入事务。我们以cglib代理为例,由于Spring的对于cglib AOP代理的实现,进入被代理方法的时候实际上已经离开了“代理这一层壳子”,可以认为代码走到的是一个朴素的bean,调用同一个bean中方法自然与代理没有半毛钱关系了。

一般对于声明式事务都是以调用另一个类的加了@Transactional注解的public方法作为入口的。

spring事务关键处理流程

EnableTransactionManagement注解导入TransactionManagementConfigurationSelector

TransactionManagementConfigurationSelector加载InfrastructureAdvisorAutoProxyCreator(但不一定是它,一般都是AnnotationAwareAspectJAutoProxyCreator),BeanFactoryTransactionAttributeSourceAdvisor,TransactionInterceptor

-AnnotationAwareAspectJAutoProxyCreator在ioc流程一个关键步骤是查找Advisor,有两个方面,第一是实现了Advisor接口的类,第二是基于注解Aspectj。关键是BeanFactoryTransactionAttributeSourceAdvisor被加载进了代理缓存

代理调用方法的时候会执行DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice,这个时候就会将我们的

BeanFactoryTransactionAttributeSourceAdvisor派上用处,最主要的还是它里面的TransactionAttributeSourcePointcut进行匹配,执行TransactionInterceptor的方法

TransactionInterceptor

@Override

@Nullable

public Object invoke(MethodInvocation invocation) throws Throwable {

// Work out the target class: may be {@code null}.

// The TransactionAttributeSource should be passed the target class

// as well as the method, which may be from an interface.

Class> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

// Adapt to TransactionAspectSupport's invokeWithinTransaction...

return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);

}

TransactionAspectSupport

@Nullable

protected Object invokeWithinTransaction(Method method, @Nullable Class> targetClass,

final InvocationCallback invocation) throws Throwable {

// If the transaction attribute is null, the method is non-transactional.

TransactionAttributeSource tas = getTransactionAttributeSource();

final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);

final PlatformTransactionManager tm = determineTransactionManager(txAttr);

final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {

// Standard transaction demarcation with getTransaction and commit/rollback calls.

TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);

Object retVal;

try {

// This is an around advice: Invoke the next interceptor in the chain.

// This will normally result in a target object being invoked.

retVal = invocation.proceedWithInvocation();

}

catch (Throwable ex) {

// target invocation exception

completeTransactionAfterThrowing(txInfo, ex);

throw ex;

}

finally {

cleanupTransactionInfo(txInfo);

}

commitTransactionAfterReturning(txInfo);

return retVal;

}

else {

final ThrowableHolder throwableHolder = new ThrowableHolder();

// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.

try {

Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {

TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);

try {

return invocation.proceedWithInvocation();

}

catch (Throwable ex) {

if (txAttr.rollbackOn(ex)) {

// A RuntimeException: will lead to a rollback.

if (ex instanceof RuntimeException) {

throw (RuntimeException) ex;

}

else {

throw new ThrowableHolderException(ex);

}

}

else {

// A normal return value: will lead to a commit.

throwableHolder.throwable = ex;

return null;

}

}

finally {

cleanupTransactionInfo(txInfo);

}

});

// Check result state: It might indicate a Throwable to rethrow.

if (throwableHolder.throwable != null) {

throw throwableHolder.throwable;

}

return result;

}

catch (ThrowableHolderException ex) {

throw ex.getCause();

}

catch (TransactionSystemException ex2) {

if (throwableHolder.throwable != null) {

logger.error("Application exception overridden by commit exception", throwableHolder.throwable);

ex2.initApplicationException(throwableHolder.throwable);

}

throw ex2;

}

catch (Throwable ex2) {

if (throwableHolder.throwable != null) {

logger.error("Application exception overridden by commit exception", throwableHolder.throwable);

}

throw ex2;

}

}

}

这次在分析这个方法,但是从事务的异常,不生效等角度来分析问题。注解事务和编程式都一样的核心思想,下面我们来分析注解事务逻辑

if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {

// Standard transaction demarcation with getTransaction and commit/rollback calls.

TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);

Object retVal;

try {

// This is an around advice: Invoke the next interceptor in the chain.

// This will normally result in a target object being invoked.

retVal = invocation.proceedWithInvocation();

}

catch (Throwable ex) {

// target invocation exception

completeTransactionAfterThrowing(txInfo, ex);

throw ex;

}

finally {

// 把上一层事务的TxInfo重新绑到ThreadLocal中

cleanupTransactionInfo(txInfo);

}

commitTransactionAfterReturning(txInfo);

return retVal;

}

请记住这几个核心的方法逻辑顺序和异常捕获哦!

protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {

if (txInfo != null && txInfo.getTransactionStatus() != null) {

if (logger.isTraceEnabled()) {

logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +

"] after exception: " + ex);

}

//事务回滚的异常支持

if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {

try {

txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());

}

catch (TransactionSystemException ex2) {

logger.error("Application exception overridden by rollback exception", ex);

ex2.initApplicationException(ex);

throw ex2;

}

catch (RuntimeException | Error ex2) {

logger.error("Application exception overridden by rollback exception", ex);

throw ex2;

}

}

else {

// We don't roll back on this exception.

// Will still roll back if TransactionStatus.isRollbackOnly() is true.

try {

txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());

}

catch (TransactionSystemException ex2) {

logger.error("Application exception overridden by commit exception", ex);

ex2.initApplicationException(ex);

throw ex2;

}

catch (RuntimeException | Error ex2) {

logger.error("Application exception overridden by commit exception", ex);

throw ex2;

}

}

}

}

事务回滚的异常支持

@Override

public boolean rollbackOn(Throwable ex) {

return (ex instanceof RuntimeException || ex instanceof Error);

}

注意点来了,仅支持运行时异常和错误机制,否则不予回滚。并进行直接条件。

AbstractPlatformTransactionManager

private void processRollback(DefaultTransactionStatus status, boolean unexpected) {

try {

//默认false

boolean unexpectedRollback = unexpected;

try {

//回调TransactionSynchronization对象的beforeCompletion方法。

triggerBeforeCompletion(status);

if (status.hasSavepoint()) {

if (status.isDebug()) {

logger.debug("Rolling back transaction to savepoint");

}

status.rollbackToHeldSavepoint();

}

// 在最外层事务边界进行回滚

else if (status.isNewTransaction()) {

if (status.isDebug()) {

logger.debug("Initiating transaction rollback");

}

// 由具体TxMgr子类实现回滚。

doRollback(status);

}

else {

// Participating in larger transaction

if (status.hasTransaction()) {

/*

* 内层事务被标记为rollBackOnly或者globalRollbackOnParticipationFailure开关开启时,给当前事务标记需要回滚。

*

* 如果内层事务显式打上了rollBackOnly的标记,最终全事务一定是回滚掉的。

*

* 但如果没有被打上rollBackOnly标记,则globalRollbackOnParticipationFailure开关就很重要了。

* globalRollbackOnParticipationFailure开关默认是开启的,也就是说内层事务挂了,最终的结果只能是全事务回滚。

* 但如果globalRollbackOnParticipationFailure开关被关闭的话,内层事务挂了,外层事务业务方法中可以根据情况控制是否回滚。

*/

if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {

if (status.isDebug()) {

logger.debug("Participating transaction failed - marking existing transaction as rollback-only");

}

// 由具体TxMgr子类实现回滚。

doSetRollbackOnly(status);

}

else {

if (status.isDebug()) {

logger.debug("Participating transaction failed - letting transaction originator decide on rollback");

}

}

}

else {

logger.debug("Should roll back transaction but cannot - no transaction available");

}

// Unexpected rollback only matters here if we're asked to fail early

if (!isFailEarlyOnGlobalRollbackOnly()) {

unexpectedRollback = false;

}

}

}

catch (RuntimeException | Error ex) {

triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);

throw ex;

}

// 回调TransactionSynchronization对象的afterCompletion方法。

triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);

// Raise UnexpectedRollbackException if we had a global rollback-only marker

if (unexpectedRollback) {

throw new UnexpectedRollbackException(

"Transaction rolled back because it has been marked as rollback-only");

}

}

finally {

cleanupAfterCompletion(status);

}

}

案例分析

2fa4920b8f9de02e55c57fb513233b1f.png

abd985cd202b5789eea0bbbe71daf316.png

有经验的同学肯定知道整个事务最终被回滚掉了, TransactionB#test并没有执行System.out.println("TransactionB#test after");

其实对于Spring事务来说,这样的结果是正确的,但对于开发者来说,这个结果确实看似有些“不能理解”。

我们不妨来分析一下原因:

首先TransactionB#test本身是直接抛出RuntimeException的,那么退栈到事务切面后,事务切面会发现需要回滚但因为TransactionB#test还不是事务的最外层边界,所以在AbstractPlatformTransactionManager#processRollback方法仅仅会调用doSetRollbackOnly(status);,子类DataSourceTransactionManager会拿出DefaultTransactionStatus中的transaction对象打上回滚标记,具体来说就是transaction对象(对于DataSourceTransactionManager来说类型是DataSourceTransactionObject)会取出ConnectionHolder,调用setRollbackOnly。我们知道这样就相当于标记是一个全局的标记了,因为只要是隶属于同一个物理事务的Spring事务都能够读到同一个ConnectionHolder。

protected void doSetRollbackOnly(DefaultTransactionStatus status) {

DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject)status.getTransaction();

if (status.isDebug()) {

this.logger.debug("Setting JDBC transaction [" + txObject.getConnectionHolder().getConnection() + "] rollback-only");

}

//关键点

txObject.setRollbackOnly();

}

回到上层事务切面,在AbstractPlatformTransactionManager#commit方法读到if(!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly())条件成立,接下来调用processRollback,由于在事务最外层边界会物理回滚掉,并且也正是到了事务最外层边界,Spring抛出UnexpectedRollbackException。

如何解决?

那么问题怎么解决呢,这个问题有好几种解决办法,但是得根据具体情况决定。

根据实际代码与业务情况处理,如果内嵌事务注解取消,Spring也不会抛出UnexpectedRollbackException。但是方法实际上并没有完整执行,所以这样的解决思路很容易导致出现不完整的脏数据。

手动控制是否回滚。如果不能接受内嵌事务挂掉的话,可以在catch块里加上TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();用于显式控制回滚。这样Spring就明白你自己要求回滚事务,而不是unexpected了。Spring也不会抛出UnexpectedRollbackException了。那么如果在上层事务中捕获到异常,真的就是不想回滚,即便上层事务发生了异常,也想要最终提交整个事务呢?如果有这样的需求的话,可以给事务管理器配置一个参数setGlobalRollbackOnParticipationFailure(false);

如果isGlobalRollbackOnParticipationFailure为false,则会让主事务决定回滚,如果当遇到exception加入事务失败时,调用者能继续在事务内决定是回滚还是继续。然而,要注意是那样做仅仅适用于在数据访问失败的情况下且只要所有操作事务能提交,这个方法也能解决,但显然影响到全局的事务属性,所以极力不推荐使用。

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;

if (defStatus.isLocalRollbackOnly()) {

if (defStatus.isDebug()) {

logger.debug("Transactional code has requested rollback");

}

processRollback(defStatus, false);

return;

}

if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {

if (defStatus.isDebug()) {

logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");

}

processRollback(defStatus, true);

return;

}

processCommit(defStatus);

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值