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 事务背后的故事
更多汪小哥

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值