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 事务背后的故事
更多汪小哥
上一篇: css照片复杂阴影制作