事务方法A调用事务方法B,方法B抛出的异常被方法A catch后会发生什么?

1,708 阅读4分钟

博客搬家-原文链接

事务方法A调用事务方法B,当方法B抛出的异常被方法A catch后会发生什么?

场景描述

在一个事务方法中调用另一个事务方法。如在ServiceAmethodA方法中调用ServiceBmethodB方法,两个方法都设置了事务,传播机制都是PROPAGATION_REQUIRED

ServiceBmethodB方法声明事务如下。

public class ServiceB{

    @Transactional(rollbackFor = Exception.class)
    public void methodB(){
        
    }
}

methodA方法中捕获methodB异常,代码如下。

public class ServiceA{
    
    public void methodA(){
        try{
            serviceB.methodB();
        }catch(Exception e){
            // do
        }
    }
}

methodA没有加事务注解,但methodA是在事务中执行的,也是因为如此,我才调试了半天Spring事务源码。其效果等同于:

public class ServiceA{
    
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void methodA(){
        try{
            serviceB.methodB();
        }catch(Exception e){
            // do
        }
    }
}

methodB方法抛出异常后,当前事务回滚,异常往外抛出,被methodA方法catch。由于methodA方法catch了异常,异常不再往外抛出,当methodA方法执行完成时,事务切面走的不是回滚逻辑,而是提交逻辑。这就出现了如下异常。

异常信息:

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

异常原因追溯

由于methodB方法抛出异常导致事务已经回滚,且当前事务被标志为仅回滚,因此当前事务只能回滚,不能再执行提交,如果执行提交,就能看到上述异常。该异常在AbstractPlatformTransactionManagerprocessRollback方法抛出。该方法源码如下。

public abstract class AbstractPlatformTransactionManage{
    private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
		try {
			boolean unexpectedRollback = unexpected;

			try {
				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");
					}
					doRollback(status);
				}
				else {
					// Participating in larger transaction
					if (status.hasTransaction()) {
						if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
							if (status.isDebug()) {
								logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
							}
							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;
			}

			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);
		}
	}
}

没有声明事务为什么会存在事务?

虽然方法没有声明事务,可是该方法却在事务中执行,那么我们可以在TransactionAspectSupportinvokeWithinTransaction方法中下断点调试。invokeWithinTransaction方法中会调用TransactionAttributeSourcegetTransactionAttribute方法获取事务的配置信息。

如使用注解声明事务时,会调用AnnotationTransactionAttributeSourcegetTransactionAttribute方法,经调试得知,这里调用的是NameMatchTransactionAttributeSourcegetTransactionAttribute方法,如下图所示。

ServiceAmethodA方法匹配了'*'这一项。可是这又是在哪里配置的呢?只要找出在哪里配置的,将配置去掉问题也就能解决了。

首先找到nameMap字段是在什么时候初始化的,什么时候赋值的。

看源码可知:在NameMatchTransactionAttributeSourcesetProperties方法中调用setNameMap方法为nameMap字段赋值,而setProperties方法由TransactionAspectSupportsetTransactionAttributes调用,该方法的源码如下。

public void setTransactionAttributes(Properties transactionAttributes) {
	NameMatchTransactionAttributeSource tas = new NameMatchTransactionAttributeSource();
	tas.setProperties(transactionAttributes);
	this.transactionAttributeSource = tas;
}

再继续查看哪里会调用TransactionAspectSupportsetTransactionAttributes方法。

最终找到是项目中配置事务拦截器时注入的。

因为我对这个项目不熟悉,所以才有这么一波源码分析的操作。

这个事务拦截器是怎么生效的呢?

这个事务拦截器是怎么生效的?答案是通过InstantiationAwareBeanPostProcessor代理bean,拦截beanpublic方法的执行,交给事务拦截器TransactionInterceptor处理。项目中的配置如下。

    @Bean
    public BeanNameAutoProxyCreator getBeanNameAutoProxyCreator() {
        BeanNameAutoProxyCreator creator = new BeanNameAutoProxyCreator();
        // 设置方法拦截器的bean名称
        creator.setInterceptorNames("getTransactionInterceptor");
        // 拦截哪些bean
        creator.setBeanNames("*Service", "*ServiceImpl");
        // 使用cglib
        creator.setProxyTargetClass(true);
        creator.setOrder(100);
        return creator;
    }

解决方案

在与同事沟通后,本来想将这些配置去掉,但去掉后会导致一些事务方法不生效,如:

public class Servie{
    
    public void method1(){
      this.method2();
    }
    
    @Transactional
    public void method2(){
        
    }
    
}

如上面代码所示,这种情况下method2方法的事务是不生效的。method1方法虽然没有加事务注解,但由于加了BeanNameAutoProxyCreator配置,等同于给该方法加了事务注解,所以methid1方法的事务生效,所以method2也能在事务中执行。

去掉配置后对系统的影响很大,事务不生效会引发很多问题,将整个系统让测试部门重新测试一遍也不现实。那么怎么解决这个问题呢?

既然所有业务类的public方法都会被放在事务中执行,那么我就添加一个注解@NotNeedTransactional,被该注解声明的方法不在事务中执行,与@Transactional的作用正好相反。这样问题就能解决。(为什么不打catch去掉?业务需要)

那么,怎么让@NotNeedTransactional注解生效呢?

继承事务拦截器,重写invoke方法,判断如果方法加了@NotNeedTransactional注解,则直接调用方法,不走切面。代码如下。

@Bean(name = "getTransactionInterceptor")
public TransactionInterceptor getTransactionInterceptor(AbstractPlatformTransactionManager transactionManager) {
    TransactionInterceptor ti = new TransactionInterceptor() {
       
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            //  有@NotNeedTransaction注解
            if (invocation.getMethod().getAnnotation(NotNeedTransaction.class) != null) {
                return invocation.proceed();
            } else {
                return super.invoke(invocation);
            }
        }
    };
    ti.setTransactionManager(transactionManager);
    ti.setTransactionAttributes(getTransactionAttributes());
    return ti;
}

总结

本篇给出的解决方案我个人也不建议使用,也是因为目前想不到完美的解决方案。

如果这个配置不去掉,未来可能遇到的问题会更多。比如可能会将原本一个小的事务变成一个大事务。