spring 事务采坑-xml注解 事务混用

Posted 汪小哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring 事务采坑-xml注解 事务混用相关的知识,希望对你有一定的参考价值。


为啥使用spring 事务会出现这么多坑?还是对于底层原理实现机制理解不够深刻导致。对于经常使用的东西,不仅仅要求能用,更多的还是需要知道其所以然。

一、坑点分析


1.1 this 调用 本类失效


如果新启动一个事物 Propagation.REQUIRES_NEW 这样的标识,this的直接调用会由于没有走代理的逻辑失效.这一点的理解没有问题 AOP 的机制是实现事务的核心,分布式事务Seata 也是通过AOP去处理,不经过代理准导致事务失效。

1.2 only public 方法事务有效


这个不是经常使用,一般都是xml 配置好的声明事务,对于是否是public的方法很少关注;无论是xml 还是 注解收集最终都是 TransactionAttribute 反映到事务属性里面去。

注解采集


代码地址: org.springframework.transaction.annotation.AnnotationTransactionAttributeSource
publicMethodsOnly – whether to support public methods that carry the Transactional annotation only (typically for use with proxy-based AOP), or protected/private methods as well (typically used with AspectJ class weaving)




AnnotationTransactionAttributeSource 的父类这里当前方法的事务熟悉,根据是否支持pulbic方法,如果yes 不是public 直接返回!


代码地址:org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource#computeTransactionAttribute

protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) 
		// Don't allow no-public methods as required.
		if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) 
			return null;
		
      ......
		return null;


xml 配置采集


代码地址: org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource
就如配置一样,通过方法名称 和 TransactionAttribute 对应的键值对信息.




无论是xml 还是 aop 本质上是基于AOP,一般的AOP都是运行时AOP,比较特殊的AspectJ 编译时增强的 AOP 框架直接改写class字节码增强

1.3 部分事务失败导致全局回滚


代码地址: org.springframework.transaction.support.AbstractPlatformTransactionManager#commit
事务的入口程序没有抛出异常要进行提交事务的时候发现,之前执行的已经抛出异常导致部分回滚标识设置为true
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only 这个异常一点十分的熟悉


触发地址:org.springframework.transaction.support.AbstractPlatformTransactionManager#processRollback
一个典型的场景如下所示,methodA and methodB 在同一个事务里面,methodB 抛出异常,导致回滚,由于不是新的事务不会直接的执行回滚

部分失败全局回滚示例


这里就会抛出异常 Transaction rolled back because it has been marked as rollback-only

ServiceA 
  @Transactional(propagation = Propagation.PROPAGATION_REQUIRED, rollbackFor = Exception.class)
  void methodA() 
     insertDb();
     try
         ServiceB.methodB();  
     catch(Exception e)
         logger.error("异常",e);
     
  
  

ServiceB   
 @Transactional(propagation = Propagation.PROPAGATION_REQUIRED, rollbackFor = Exception.class)
 void methodB()  
     throw new BizException("业务异常");
   


methodB 事务修改


methodB 设置为新事务、挂起不影响入口调用的事务,即使异常了也不会回滚,通常情况下都try catch 了捕获了认为这里挂了不影响我主流程的业务,不应导致全局回滚。

配置部分失败不进行全局回滚


globalRollbackOnParticipationFailure 默认为true

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
        <!-- https://www.iteye.com/blog/jsczxy2-1773795 -->
        <property name="globalRollbackOnParticipationFailure" value="false" />
</bean>


spring的事务中程序控制事务成功失败 可以参考这个连接查看详情。
具体逻辑就是一个老的事务如果出现异常导致进入回滚这里的逻辑,判断是老事务,全局是否是否回滚,如果不尽兴回滚就不设置 getConnectionHolder().setRollbackOnly(); 当前连接回滚的熟悉,这样在事务的入口就commit 检测是否进行全局回滚标识就不会异常了。

//org.springframework.transaction.support.AbstractPlatformTransactionManager#processRollback
private void processRollback(DefaultTransactionStatus status) 
    if (status.isNewTransaction()) 
        //新事务执行回滚逻辑
        if (status.isDebug()) 
            logger.debug("Initiating transaction rollback");
        
        doRollback(status);
     else 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");
            
        
    

ServiceA 
  @Transactional(propagation = Propagation.PROPAGATION_REQUIRED, rollbackFor = Exception.class)
  void methodA() 
     insertDb();
     try
         ServiceB.methodB();  
     catch(Exception e)
         logger.error("异常",e);
     
  
  

ServiceB   
 @Transactional(propagation = Propagation.PROPAGATION_REQUIRED, rollbackFor = Exception.class)
 void methodB()  
     throw new BizException("业务异常");
   


1.4 xml 注解事务混用问题


先来张图,这张图是故意设置了一个类的调用同时存在xml 和注解事务配置,发挥一下想象力,如果两个org.springframework.transaction.interceptor.TransactionInterceptor 的顺序不一样带来啥样的变化,Propagation 不同带来的变化???感觉都采坑了不少,看了不少的博客,有的通过order的顺序进行解决,由于spring 实现上虽然都是搜集到了TransactionAttribute 但是注解和xml 并没有统一起来导致拦截了两次 order 越小的 越先执行。
下面的示范默认不打开globalRollbackOnParticipationFailure

ServiceA 
  @Transactional(propagation = Propagation.PROPAGATION_REQUIRED, rollbackFor = Exception.class)
  void methodA() 
     insertDb();
     try
         ServiceB.methodB();  
     catch(Exception e)
         logger.error("异常",e);
     
  
  

ServiceB   
 @Transactional(propagation = Propagation.PROPAGATION_NEW, rollbackFor = Exception.class)
 void methodB()  
     throw new BizException("业务异常");
   


xml(order 1)->注解(order 2)


xml(PROPAGATION_REQUIRED)
注解(PROPAGATION_NEW)




执行过程分析:methodA 创建一个事务,order 越小越先执行,xml 使用之前的事务,注解创建一个事务,事务回滚,xml 捕获到异常标记全局回滚标识,methodA 提交事务发现全局回滚打标,回滚事务。这里发现如果注解标识的新事务在最外层就没有事。

如果两个都是PROPAGATION_NEW


执行过程分析:methodA 创建一个事务,order 越小越先执行,xml 创建新的事务事务获取连接,注解创建新的事务,获取连接,发现没有如果xml and 注解都是使用的情况下明明一个方法的执行,平白无故的占用了两个连接数。这个解释对?欢迎指点。


解决方案


这个解决方案同事们提供的,都是大牛
在xml 配置表达式中过滤掉类、方法上面有事务注解的

<aop:config>
    <aop:pointcut id="ao_bo"
                    expression="(execution(* com.xyz.myapp.service.*.*(..)))and !(@annotation(org.springframework.transaction.annotation.Transactional) or @within(org.springframework.transaction.annotation.Transactional)))"/>
    <aop:advisor pointcut-ref="ao_bo" advice-ref="defaultTxAdvice"/>
</aop:config>


还有一种解决方案比较复杂,实现将xml 和注解的TransactionAttribute 合并到一起,首先是要注解的,这个其实应该让spring 官方支持一下。
spring声明事务和注解事务并存的问题


1.5 注解获取 TransactionAttribute


@Transactional这个事务注解对继承的问题 @Transactional这个注解继承的问题特别的多,而且不同的版本实现的方式不太一样,但是总体上来说 写在目标类的方法上总没错!
代码地址: org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource#computeTransactionAttribute
第一步先找目标方法的上寻找注解,第二部 然后找targetClass 上寻找注解

private TransactionAttribute computeTransactionAttribute(Method method, Class targetClass) 
    // Don't allow no-public methods as required.
    if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) 
        return null;
    

    // The method may be on an interface, but we need attributes from the target class.
    // If the target class is null, the method will be unchanged.
    Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
    // If we are dealing with method with generic parameters, find the original method.
    if (JdkVersion.isAtLeastJava15()) 
        specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
    

    // First try is the method in the target class.
    TransactionAttribute txAtt = findTransactionAttribute(specificMethod);
    if (txAtt != null) 
        return txAtt;
    

    // Second try is the transaction attribute on the target class.
    txAtt = findTransactionAttribute(specificMethod.getDeclaringClass());
    if (txAtt != null) 
        return txAtt;
    

    if (specificMethod != method) 
        // Fallback is to look at the original method.
        txAtt = findTransactionAttribute(method);
        if (txAtt != null) 
            return txAtt;
        
        // Last fallback is the class of the original method.
        return findTransactionAttribute(method.getDeclaringClass());
    
    return null;
	


findTransactionAttribute 最终调用,不同的版本实现不一样。4.x版本是get 5.x是find 差距好大啊!!!
• 4.x 版本 AnnotatedElementUtils.getMergedAnnotationAttributes 只查找当前元素以及元注解(如果是类有继承 @Inherited注解 继承)
• 5.x 版本 AnnotatedElementUtils.findMergedAnnotationAttributes 查找当前类->遍历元注解查找当前注解->查找当前元素的所有接口->查找当前父类方法

public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) 
		AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
				ae, Transactional.class, false, false);
		if (attributes != null) 
			return parseTransactionAnnotation(attributes);
		
		else 
			return null;
		


自己测试看一下 4.3.20

@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public interface AnnotationsService 

    @Transactional(propagation = Propagation.SUPPORTS)
    public void test();


public class AnnotationsServiceImpl implements AnnotationsService 

    @Override
    public void test() 

    


@Test
public void testServices() throws Exception 
    ProxyFactory proxyFactory = new ProxyFactory();
    proxyFactory.setTargetClass(AnnotationsServiceImpl.class);
    proxyFactory.setTarget(new AnnotationsServiceImpl());
    Object proxy = proxyFactory.getProxy();

    Class<?> targetClass = AopUtils.getTargetClass(proxy);

    Method test = ReflectionUtils.findMethod(targetClass, "test");

    AnnotationTransactionAttributeSource attributeSource = new AnnotationTransactionAttributeSource(true);

    TransactionAttribute transactionAttribute = attributeSource.getTransactionAttribute(test, targetClass);

    log.info("transactionAttribute=", transactionAttribute.toString());



//5.x 22:14:41.184 [main] INFO SpringTest - transactionAttribute=PROPAGATION_SUPPORTS,ISOLATION_DEFAULT; ''
//4.x  null

1.6 特殊异常不回滚


@Transactional 默认情况会对于RuntimeException、Error 进行回滚 仔细看继承图

public boolean rollbackOn(Throwable ex) 
		return (ex instanceof RuntimeException || ex instanceof Error);

更多

系列文章一: spring 注解 事务,声明事务共存—有bug
系列文章二:spring 注解 事务,声明事务混用–解决问题
系列文章三:spring 事务采坑-xml注解 事务混用
系列文章四: spring 事务背后的故事
更多汪小哥

以上是关于spring 事务采坑-xml注解 事务混用的主要内容,如果未能解决你的问题,请参考以下文章

Spring基础(十六):Spring事务管理注解方式和XML配置方式

spring事务管理,xml配置aop事务和注解配置aop事务

Spring 事务控制 -- 基于注解的声明式事务控制

Spring事务操作---概念--场景---声明式事务管理---事务参数--注解方式---xml方式

Springday03 AOPSpring声明式事务Spring编程式事务

spring--声明式事务(包含基于注解和基于xml文件的配置方式)