@Transactional 自调用失效问题解析

简介: @Transactional 自调用失效问题解析

一、背景

”脏脏包“在技术群里问了一个问题:”大家有在项目中遇到这样的场景吗 在一个service层重写的方法中调用一个私有方法。 service重写的方法不加事务 私有方法想加入事务 他去调用私有方法时 私有方法需要被事务控制“ 。


这个问题比较典型,面试时也经常被问到,在此简单整理一下。


二、Spring注解方式的事务实现机制

在应用调用声明@Transactional 的目标方法时,Spring Framework 默认使用 AOP 代理,在代码运行时生成一个代理对象,根据@Transactional 的属性配置信息,这个代理对象决定该声明@Transactional 的目标方法是否由拦截器 TransactionInterceptor 来使用拦截,在 TransactionInterceptor 拦截时,会在在目标方法开始执行之前创建并加入事务,并执行目标方法的逻辑, 最后根据执行情况是否出现异常,利用抽象事务管理器(图 2 有相关介绍)AbstractPlatformTransactionManager 操作数据源 DataSource 提交或回滚事务, 如图 1 所示。





图 1. Spring 事务实现机制


Spring AOP 代理有 CglibAopProxy 和 JdkDynamicAopProxy 两种,图 1 是以 CglibAopProxy 为例,对于 CglibAopProxy,需要调用其内部类的 DynamicAdvisedInterceptor 的 intercept 方法。


对于 JdkDynamicAopProxy,需要调用其 invoke 方法。




三、分析

3.1 为什么@Transactional 只能应用到 public 方法才有效?

3.1.1 从理论角度

理论上@Transactional 注解是为了进行事务增强。


JDK 动态代理,比如需事先接口才行,因此必然是 public的。


AspectJ 动态代理,是基于类的代理,如果该类的方法为私有,那么子类中就无法重写和调用。



3.1.2 从源码角度

这是因为在使用 Spring AOP 代理时,Spring 在调用在的 TransactionInterceptor 在目标方法执行前后进行拦截之前,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource(Spring 通过这个类获取 @Transactional 注解的事务属性配置属性信息)的 computeTransactionAttribute 方法。


其源码如下:


private TransactionAttribute computeTransactionAttribute(Method method, Class targetClass) {
// 关键在这里
            if (this.allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
                return null;
            } else {
                Class userClass = ProxyUtils.getUserClass(targetClass);
                Method specificMethod = ClassUtils.getMostSpecificMethod(method, userClass);
                specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
                TransactionAttribute txAtt = null;
                if (specificMethod != method) {
                    txAtt = this.findTransactionAttribute(method);
                    if (txAtt != null) {
                        return txAtt;
                    }
                    txAtt = this.findTransactionAttribute(method.getDeclaringClass());
                    if (txAtt != null || !this.enableDefaultTransactions) {
                        return txAtt;
                    }
                }
                txAtt = this.findTransactionAttribute(specificMethod);
                if (txAtt != null) {
                    return txAtt;
                } else {
                    txAtt = this.findTransactionAttribute(specificMethod.getDeclaringClass());
                    if (txAtt != null) {
                        return txAtt;
                    } else if (!this.enableDefaultTransactions) {
                        return null;
                    } else {
                        Method targetClassMethod = this.repositoryInformation.getTargetClassMethod(method);
                        if (targetClassMethod.equals(method)) {
                            return null;
                        } else {
                            txAtt = this.findTransactionAttribute(targetClassMethod);
                            if (txAtt != null) {
                                return txAtt;
                            } else {
                                txAtt = this.findTransactionAttribute(targetClassMethod.getDeclaringClass());
                                return txAtt != null ? txAtt : null;
                            }
                        }
                    }
                }
            }
        }

非公有函数事务属性信息返回null




3.2 为什么自调用无效?

在 Spring 的 AOP 代理下,只有目标方法由外部调用,目标方法才由 Spring 生成的代理对象来管理,这会造成自调用问题。


若同一类中的其他没有@Transactional 注解的方法内部调用有@Transactional 注解的方法,有@Transactional 注解的方法的事务被忽略,不会发生回滚。


@Service

public class OrderService {

   private void insert() {

insertOrder();

}

@Transactional

   public void insertOrder() {

       //SQL操作

      }

}

insertOrder 尽管有@Transactional 注解,但它被内部方法 insert 调用,事务被忽略,出现异常事务不会发生回滚。



四、解决方法

4.1 可以使用ApplicatonContextHolder 工具类,从上下文中获取当前bean,再调用。


4.2 可以使用上下文工具类获取当前对象的代理类  @EnableAspectJAutoProxy (exposeProxy = true) 然后通过下面方法获取代理对象,然后再调用


@Service

public class OrderService {

 public void insert() {

   OrderService proxy = (OrderService) AopContext.currentProxy();

      proxy.insertOrder();

   }

   @Transactional

   public void insertOrder() {

       //SQL操作

      }

}

4.3  maven中加入spring-aspects 和 aspectjrt 的依赖以及 aspectj-maven-plugin插件



注:


第二节和第三节部分内容转载自:


透彻的掌握 Spring 中@transactional 的使用


创作不易,如果觉得本文对你有帮助,欢迎点赞,欢迎关注我,如果有补充欢迎评论交流,我将努力创作更多更好的文章。

————————————————

版权声明:本文为CSDN博主「明明如月学长」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/w605283073/article/details/93674200

相关文章
|
XML Java 数据格式
SpringBean的生命周期
SpringBean的生命周期
257 0
|
数据采集 SQL 存储
一种基于Hive的数据质量检核方法
本发明提出了一种数据质量检核方法、装置、设备及可读存储介质,所述方法包括如下步骤:1)根据质量检核需求,对多业务系统数据进行关联建模,生成关联建模结果;2)根据所述关联建模结果,配置数据质量检核规则,获取数据配置结果;3)将所述数据配置结果导入规则解析器,生成检核脚本;4)将所述检核脚本导入脚本执行器,生成检核明细表;5)对所述检核明细表进行汇总统计,生成检核结果报告。本发明通过针对不同的检核要求,将多业务系统数据进行临时关联汇总,初步对待检核数据进行筛选,限定数据范围,可以大大提升质量检核结果的准确性和有效性,以及降低使用和维护成本。
1377 0
一种基于Hive的数据质量检核方法
|
域名解析 Cloud Native jenkins
【Drone+Gitlab】一条龙服务,直接起飞 — 从介绍->部署->配置->写.drone.yml流水线+常见的报错解决
gitlab+drone部署安装,编写.drone.yml流水线 drone是一个持续集成化工具,gitlab是一个代码仓库,.drone.yml流水线编写 fatal: unable to access,could not resolve host 克隆地址连接不上(修改默认clone克隆),没有Trusted选项,启动drone-server时添加(--env=DRONE_USER_CREATE=username:root,admin:true) .drone.yml文件中sed命令报错
2418 0
【Drone+Gitlab】一条龙服务,直接起飞 — 从介绍->部署->配置->写.drone.yml流水线+常见的报错解决
|
3月前
|
开发框架 前端开发 Java
Spring篇
Spring是一个用于简化Java企业级应用开发的开源框架,核心功能包括控制反转(IoC)和面向切面编程(AOP)。它通过管理对象生命周期、解耦组件、支持多种注入方式及提供如MVC、事务管理等模块,提升开发效率与代码质量。常用于构建轻量、灵活、易维护的企业级应用程序。
219 0
|
Java 数据库 Spring
@Transactional 失效场景介绍
【2月更文挑战第5天】
1205 1
@Transactional 失效场景介绍
|
11月前
|
JavaScript Java 关系型数据库
Spring事务失效的8种场景
本文总结了使用 @Transactional 注解时事务可能失效的几种情况,包括数据库引擎不支持事务、类未被 Spring 管理、方法非 public、自身调用、未配置事务管理器、设置为不支持事务、异常未抛出及异常类型不匹配等。针对这些情况,文章提供了相应的解决建议,帮助开发者排查和解决事务不生效的问题。
1346 1
|
9月前
|
IDE Java 开发工具
@Transactional 你真的用对了吗?
在日常开发中,`@Transactional`注解常用于声明式事务管理,但其原理和使用不当可能引发问题。本文通过一个实际场景探讨了自调用方法时事务不生效的问题,并分析了潜在风险:数据不一致。为解决此问题,提供了三种方案:1) 将方法移动到其他服务类;2) 使用`AopContext.currentProxy()`获取代理对象;3) 通过`ApplicationContext`获取Bean。最终建议尽量避免自调用事务操作,确保数据一致性。
322 6
|
机器学习/深度学习 自然语言处理 语音技术
FunAudioLLM与其他语音模型多维度对比简析
FunAudioLLM与其他语音模型多维度对比简析
398 13
|
JSON Java 数据格式
Java系列之:生成JSON字符串
这篇文章介绍了两种在Java中生成JSON字符串的方法:使用`JSONObject`类及其`toJSONString`方法来动态生成,以及手动拼接字符串的方式来创建JSON格式的字符串。
Java系列之:生成JSON字符串