@Transactional失效的10种场景

news2024/11/28 22:31:51

@Transactional失效的场景都有哪些呢?本章节针对@Transactional的问题,做以下总结整理。

图片

  • 1、同一个类中,方法内部调用事务失效

  • 2、事务方法被final、static修饰

  • 3、当前类没有被Spring管理

  • 4、非public修饰的方法(存在版本差异)

  • 5、事务多线程调用

  • 6、数据库本身不支持事务

  • 7、异常被方法内部try catch捕获,没有重新抛出

  • 8、嵌套事务回滚多了

  • 9、rollbackFor属性设置错误

  • 10、设置不支持事务的传播机制

以上我们列举了10种场景,接下来我们针对不同的场景来具体的分析下。

SpringBoot版本:3.1.3

SpringFramework版本:6.0.11

场景分析

一、代理不生效导致

1、同一个类中,方法内部调用事务失效

场景一:同一个类中,addOrder()方法无事务,addOrder2()方法存在事务,addOrder()调用addOrder2()。

  • @Servicepublic class OrderServiceImpl { @Autowired private OrderMapper orderMapper; public int addOrder(Double amount, String address) {        int order = addOrder2(amount, address); return order; } @Transactional public int addOrder2(Double amount, String address) { int order = orderMapper.addOrder(amount, address);        int i = order / 0; return order; }}

我们通过外部方法调用addOrder()方法,来完成数据库的插入,通过手动的设置异常order/0,来观察addOrder2()方法中的数据是否会正常回滚。

图片

通过数据库的结果显示,数据正常入库了,证明了我们的事务并未生效。

场景二:同一个类中,addOrder()和addOrder2()都存在事务,addOrder()调用addOrder2()。

@Servicepublic class OrderServiceImpl {
    @Autowired    private OrderMapper orderMapper;
    @Transactional    public int addOrder(Double amount, String address) {        int order = addOrder2(amount, address);        return order;    }
    @Transactional    public int addOrder2(Double amount, String address) {        int order = orderMapper.addOrder(amount, address);        int i = order / 0;        return order;    }}

order/0 产生异常之后,通过数据库的结果显示,发现数据并未入库,说明事务生效了。

通过两种场景对比,我们发现并不是所有同一个类,方法的内部调用事务都会失效。

那接下来,我们就具体聊聊场景一:事务不生效的原因。

图片

外部代码调用addOrder()方法时,并没有直接进入目标方法,而是首先进入了DynamicAdvisedInterceptor的intercept()方法中。

这是因为我们的orderService的bean在项目在项目启动的时候就生成一个代理对象,这里调用addOrder()方法的,其实是代理对象,而代理对象对目标方法的调用,会转发进入CGLIB动态代理类的intercept()方法中进行增强。

我们来看下DynamicAdvisedInterceptor的intercept()逻辑。

图片

通过本地debug 我们可以看到如果addOrder()方法未加@Transactional的情况下,会直接进入45行的AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse)逻辑中。

图片

而该方法,则直接通过mehod.invoke()完成了对目标方法的调用,没有做额外的逻辑处理。

而在addOrder()方法内部调用addOrder2()方法的时候,也是对目标方法的直接调用。好像addOrder2()方法上@Transactional注解的存在没有起什么作用。

当addOrder2()方法发生异常之后,也就没有什么机制来处理事务的回滚。

那如果我们把controller中的调用换成orderService.addOrder2(),在addOrder2()方法上打断点,看下代码的处理逻辑。

图片

在方法调用栈中,我们发现了TransactionInterceptor事务拦截器。

而通过TransactionInterceptor 这个机制,我们完成了对目标方法的增强,例如事务控制,主要处理逻辑在TransactionAspectSupport.invokeWithinTransation()中。

图片

主要内容如下:

  • createTransactionIfNecessary() : 创建一个标准事务

  • invocation.proceedWithInvocation:调用拦截器链条中的下一个拦截器,最终目标方法会被调用

  • completeTransationAfterThrowing():调用出现异常 进行处理

  • cleanupTransactionInfo:清除事务信息

  • commitTransactionAfterRetruning():提交事务commit

当addOrder2()异常发生时,TransactionInterceptor会对事务做异常处理。

通过以上的内容分析,我们很清晰的看出来,只有当调用addOrder2()方法是通过代理类进行的时候,才能触发事务的拦截。

2、事务方法被final、static修饰

示例代码:

@Servicepublic class OrderServiceImpl {
    @Autowired    private OrderMapper orderMapper;
    @Transactional    public final int addOrder(Double amount, String address) {        int order = orderMapper.addOrder(amount, address);        int i = order / 0;        return order;    }    @Transactional    public int addOrder2(Double amount, String address) {        int order = orderMapper.addOrder(amount, address);        int i = order / 0;        return order;    } }

失效原因:CGLIB是通过生成目标类子类的方式生成代理类的,被final、static修饰的方法,无法被子类重写。

当通过外部接口调用addOrder()方法时,我们代理类不是走DynamicAdvisedInterceptor拦截器,而是直接调用了目标方法。

图片

为什么会出现这个现象呢?

在SpringBoot启动,我们创建bean的过程中,会给orderServiceImpl类生成一个代理类,在创建代理类的过程中,在ProxyCallbackFilter.accept()方法中,会指定目标方法执行的时候,触发的拦截器数组中的顺序。

图片

例如addOrder2()方法,filter.accept()方法返回的是0。

图片

在orderServiceImp代理类中0 的位置,则就是DynamicAdvisedInterceptor拦截器。

但是addOrder()方法,却没有出现在Enhancer.emitMethods()的方法入参中actualMethods。也就并未执行filter.accept()逻辑,获取代理类中拦截器的位置。

actualMethods为什么未出现final、static修饰的方法呢?

图片

我们继续顺着源码,往上着,发现在Enhancer.getMethods获取方法的逻辑中,源码是过滤掉了static、和final修饰的方法。

图片

图片

  • 1、如果rejectMask为8,表示修饰符中是否包含static;如果结果==0为true,则表示不包含,反之false则包含,则从集合methods中剔除。

  • 2、如果rejectMask为16,表示修饰符中是否包含final;如果结果==0为true,则表示不包含,反之false则包含,则从集合methods中剔除。

这里补充一个知识点:

Java中修饰符的数值大小

  • public:1

  • private:2

  • protected:4

  • static:8

  • final:16

那是不是表示,我们的代理类中是没有也final、static修饰的方法?

通过本地生成的动态代理类,我们也验证了这一说法,我们新生成代理类orderServiceImpl中,确实没有被final修饰的方法addOrder(),只有addOrder2()。

图片

知识扩展:如何本地生成动态代理类?(用于springBoot中的bean动态代理类的生成)

1、定义一个工具类,用于文件输出

public class ProxyFileOutUtils {    public static void saveProxyFile(String className, byte[] bytes){        if (className.equals("com.example.service.OrderServiceImpl$$SpringCGLIB$$0")){            String fileName = className + ".class";            File file = new File("src/main/java/com/example/out", fileName);            try (FileOutputStream fos = new FileOutputStream(file)) {                fos.write(bytes);                System.out.println("Proxy class written to: " + fileName);            } catch (IOException e) {                e.printStackTrace();            }        }    }}

2、在类AbstractClassGenerator.generate()里边,打上断点,

图片

在断点里边定义自定义处理逻辑:ProxyFileOutUtils.saveProxyFile(className, b)

springBoot启动的时候,就会生成代理类,我们这里主要是获取到代理类的名称calssName,和代理类的字节数组b,来本地生成代理类。

图片

3、当前类没有被Spring管理

例如:Service类中没有@Service注解

public class OrderServiceImpl {    @Autowired    private OrderMapper orderMapper;    @Transactional    public int addOrder(Double amount, String address) {        int order = orderMapper.addOrder(amount, address);        int i = order / 0;        return order;    }}

从以上的两种场景分析,我们得知@Transactional事务生效的前提条件是需要代理类对目标方法的调用,才能触发事务处理,而代理类是在springBoot启动时创建bean的时候,处理的。如果我们的类没有@Service注解,就不会交给spring容器初始化处理,也就无法为目标类生成代理类。

二、框架或者底层不支持

4、非public修饰的方法(存在版本差异)

示例代码:

  • @Servicepublic class OrderServiceImpl { @Autowired private OrderMapper orderMapper; @Transactional protected int addOrder(Double amount, String address) { int order = orderMapper.addOrder(amount, address); int i = order / 0; return order; }}

通过外部接口的调用,异常之后,发现事务正常回滚,数据库数据并没有入库,说明事务是生效的。

springframework的版本:6.0.11

不是说非public修饰的方法,事务不生效吗?

通过本地debug分析源码得知,在spring版本6.0.11中是支持proected修饰的方法的。

图片

在创建事务属性AbstractFallbackTransactionAttributeSource.computeTransactionAttribute()的逻辑中,allowPublicMethodOnly()方法的返回值,默认是false,即使在第二个条件中,方法修饰符不是public,也不会返回null,事务属性TransactionAttribute正常被创建。

而allowPublicMethodOnly()的值publicMethodOnly是在transactionAttributeSource的bean初始化的时候赋值的,默认是false。

图片

对比了spring5.3.25的版本,发现之前的版本,publicMethodOnly默认值确实为true。

图片

图片

如果publicMethodOnly为true的情况下,AbstractFallbackTransactionAttributeSource.computeTransactionAttribute()方法中,如果第二个条件,方法的修饰符非pulic,if中条件就会成立,则事务属性返回null,就会导致@Transactional事务失效。

图片

5、事务多线程调用

示例代码:

@Servicepublic class OrderServiceImpl {    @Autowired    private OrderMapper orderMapper;    @Autowired    private SysUserServiceImpl sysUserService;
    @Transactional(rollbackFor = Exception.class)    public int addOrder(Double amount, String address){        int order = orderMapper.addOrder(amount, address);        new Thread(() -> {            sysUserService.saveUser();        }).start();
        return order;    }}
  • @Servicepublic class SysUserServiceImpl { @Transactional public void saveUser(){ throw new RuntimeException("新增人员失败"); }}

当我们使用外部接口调用addOrder()方法时,sysUserService.saveUser()方法发生异常。但是发现orderMapper.addOrder()的数据正常入库了,事务失效。

图片

通过本地debug和分析源码得知,我们的事务是给线程绑定的(TransactionAspectSupport.prepareTransactionInfo())。

图片

在执行sysUserService.saveUser()目标方法的时候,我们通过代理类执行逻辑,获取到的事务AbstractPlatformTransactionManager.getTransaction()其实是重新创建的一个事务。

图片

因此当saveUser()方法发生异常时,addOrder()方法的事务未能同步回滚数据。

6、数据库本身不支持事务

Spring事务的底层,还是依赖于数据库本身的事务支持。在MySQL中,Myisam存储引擎是不支持事务的,InnoDB引擎才支持事务。

这种问题出现的概率很小,在Mysql5之后,默认情况下是使用的InnoDB引擎存储

如果是历史项目,发现事务怎么配置都不生效,确认下你的存储引擎是否支持事务

图片

三、开发使用不当引发

7、异常被方法内部try catch捕获,没有重新抛出

示例代码:

  • @Servicepublic class OrderServiceImpl { @Autowired private OrderMapper orderMapper; @Transactional public int addOrder(Double amount, String address) { int order = 0; try { order = orderMapper.addOrder(amount, address); int i = order / 0; } catch (Exception e) { } return order; }}

当外部接口调用addOrder()方法,异常发生的时候,数据库数据正常入库了,事务未生效。

图片

在TransactionAspectSupport.invokeWithinTransaction()方法中,我们可以看到以下逻辑。

图片

try catch 会监控invocation.proceedWithInvocation()方法的执行,也就是目标方法的执行结果,如果我们的目标方法addOrder()手动捕获了异常,没有抛出,在invokeWithinTransaction方法中的try catch就无法捕获到,它会认为逻辑正常,最终会调用commitTransactionAfterReturning()提交事务,从而导致事务控制失效。

8、嵌套事务回滚多了

示例代码:​​​​​​​

@Servicepublic class OrderServiceImpl {    @Autowired    private OrderMapper orderMapper;    @Autowired    private SysUserServiceImpl sysUserService;
    @Transactional(rollbackFor = Exception.class)    public int addOrder(Double amount, String address){        int order = orderMapper.addOrder(amount, address);        sysUserService.saveUser();        return order;    }}@Servicepublic class SysUserServiceImpl {
    @Transactional    public void saveUser(){        throw new RuntimeException("新增人员失败");    }}

存在以上场景,即使sysUserService.saveUser()方法发生异常,我们期望orderMapper.addOrder()方法执行的结果也正常入库。

当出现嵌套事务发生异常的时候,实际上两个方法的事务都会进行回滚。

解决办法:

1、addOrder()方法使用trye/catch包住

2、saveUser()方法的事务传播机制调整为Propagation.REQUIRES_NEW

以上两种方式都可以实现我们需要的场景。

9、rollbackFor属性设置错误

示例代码:

​​​​​​​

@Servicepublic class OrderServiceImpl {    @Autowired    private OrderMapper orderMapper;
    @Transactional    public int addOrder(Double amount, String address) throws FileNotFoundException {        int order = orderMapper.addOrder(amount, address);        throw new FileNotFoundException("11111");    }}

在demo中,我们手动的抛出FileNotFountException异常,这是一个IOException异常。

当我们使用@Transactional,在未对rollbackFor做配置的情况下,默认是支持对Runtime和Error异常的回滚的。

图片

但当我们的demo中的异常是IOException的时候。

DefaultTransactionAttribute.rollbackOn()方法返回false,就会导致completeTransactionAfterThrowing()方法调用694行逻辑txInfo.getTransactionManager().commit(txInfo.getTransactionStatus())提交事务。

图片

图片

从源码694行else逻辑的注释上我们也能看出,无法回滚异常。

所以通常情况下,我们建议指定@Transactional(rollbackFor = Exception.class)的方式进行异常捕获。

10、设置不支持事务的传播机制

Spring支持了7种传播机制,分别为:

图片

上面不支持事务的传播机制为:PROPAGATION_SUPPORTS,PROPAGATION_NOT_SUPPORTED,PROPAGATION_NEVER。

如果配置了这三种传播方式的话,在发生异常的时候,事务是不会回滚的。

示例代码:​​​​​​​

@Servicepublic class OrderServiceImpl {    @Autowired    private OrderMapper orderMapper;
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.SUPPORTS)    public int addOrder(Double amount, String address) throws FileNotFoundException {        int order = orderMapper.addOrder(amount, address);        throw new FileNotFoundException("11111");    }}

图片

在处理事务异常回滚AbstractPlatformTransactionManager.processRollback()的逻辑中,这三种传播机制,就只是打印了下debug日志,没有进行真正的回滚,从日志记录信息中我们可以看到:

logger.debug("Should roll back transaction but cannot - no transaction available");

应该回滚但是不能,因为没有可用的事务。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1586814.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring5深入浅出篇:bean的生命周期

Spring5深入浅出篇:bean的生命周期 什么是对象的⽣命周期 指的是⼀个对象创建、存活、消亡的⼀个完整过程 为什么要学习对象的⽣命周期 由Spring负责对象的创建、存活、销毁,了解⽣命周期,有利于我们使⽤好Spring为我们创建的对象 ⽣命周期的3个阶段…

知识图谱的演进与基于 OpenSPG+TuGraph 的推理实践

本文为蚂蚁集团开发工程师王少飞在TuGraph Meetup北京站的演讲,主要介绍了蚂蚁知识图谱平台经过多年金融领域业务沉淀的知识语义增强可编程框架SPG,及TuGraph作为执行引擎在图谱推理过程的作用。 作者介绍: 王少飞,蚂蚁知识图谱推…

微信小程序抓包教程

前言 笔者最近在研究如何又简单又精准的对微信小程序进行HTTP/HTTPS流量抓包,然后进行渗透测试。研究了几天,发现了一个最适合自己的方法,特此进行记录。 前期准备 流量工具:Proxifier抓包工具:BurpSuite电脑端微信…

选择程序员是为什么?

本章节是关于为什么会选择一名程序员的经验分享 首先,我为什么会选择这个方向,可能是因为钱多,学东西不就是为了赚钱嘛?这是一点,不过最让我接收这个行业的是好奇世界的新大陆,可以简单的说就是&#xff0c…

蓝桥杯第十届c++大学B组详解

目录 1.组队 2.年号字符 3.数列求值 4.数的分解 5.迷宫 6.特别数的和 7.完全二叉树的权值 8.等差数列 9.后缀表达式 10.灵能传输 1.组队 题目解析:就是在个篮球人中选择这个最大的成绩,每个人只能选择一次不能重复选择。选满5人之后的成绩是最…

洛谷P1115最大子段和[神奇的题目]

啊,好久没更新了 往期内容推荐: 欧几里得算法-----无聊的军官pro max版本-CSDN博客 (并不怎么华丽的分割线) 一,题目描述 给出一个长度为 n 的序列 a,选出其中连续且非空的一段使得这段和最大。 ## 输…

npm i -g nodemon 遇到的下载卡住及运行权限问题解决记录

一、下载nodemon原因 nodemon作用:用node环境运行js文件时可以实时刷新运行出结果 (即修改js代码后不需再手动重新运行js文件) 二、下载卡住 reify:semver:timing reifyNode:node_modules/nodemon Completed 卡住位置:reify:semver: timing reifyNode…

C语言—每日选择题—Day69

第一题 1、以下程序的输出结果是( ) int main() {char arr[2][4];strcpy (arr[0],"you");strcpy (arr[1],"me");arr[0][3]&;printf("%s \n",arr);return 0; } A: you&me B: you C: me D: err 答案及解析 A 这里重…

多项式拟合:最小二乘、拉格朗日插值、牛顿插值

多项式拟合是一种常用的数学和机器学习方法,它使用一个多项式函数来拟合一组数据点。多项式拟合的目的是在众多的样本点中找出满足样本点分布的多项式。它基于多项式函数的性质,可以表示为 yw0w1xw2x2…wnxn 的形式,其中 y 是因变量&#xff…

学习大数据,所需要的java(Maven)基础(1)

文章目录 使用Maven的优势第三方jar包添加第三方jar包获取jar包之间的依赖关系jar包之间的冲突处理将项目拆分成多个工程模块 实现项目的分布式部署Maven是什么自动化构建工具构建的概念构建环节自动化构建 Maven如何使用安装Maven核心程序maven联网问题Maven中的settings配置在…

【vue】v-bind动态属性绑定

v-bind 简写:value <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><styl…

4月跨境选品攻略:四类爆品机会,抢占市场流量!

我们来聊聊4月份海外电商的选品&#xff0c;4月份开始进入年春季&#xff0c;在西方人们开始更多的户外活动&#xff0c;阳光更加明媚&#xff0c;所以哪些产品在这个季节很好销售呢&#xff1f; 第一类&#xff1a;墨镜 谷歌搜索过去五年的搜索数据显示&#xff0c;每年4月份…

变速齿轮原理分析及检测方案

据FairGaurd游戏加固观察&#xff0c;在游戏面临的众多外挂风险中&#xff0c;除了常见的内存修改、模拟点击、注入挂等作弊手段&#xff0c;黑灰产还常用「变速」手段实现作弊。 游戏安全风险分布占比图 「变速」顾名思义是指改变游戏运行速度。具体原理为通过 HOOK 游戏内时…

遥控小车电子方案

遥控小车的功能开发主要包括以下几个方面&#xff1a; 1.基本功能开发&#xff1a; 前进、后退、左转、右转&#xff1a;通过遥控器上的控制按钮&#xff0c;实现小车的前进、后退、左转和右转。加速、减速&#xff1a;通过遥控器上的油门控制按钮&#xff0c;实现小车的加速…

Can Transformer and GNN Help Each Other?

ABSTRACT 尽管 Transformer 在自然语言处理和计算机视觉方面取得了巨大成功&#xff0c;但由于两个重要原因&#xff0c;它很难推广到中大规模图数据&#xff1a;(i) 复杂性高。 (ii) 未能捕获复杂且纠缠的结构信息。在图表示学习中&#xff0c;图神经网络&#xff08;GNN&…

论文笔记:NEFTune: Noisy Embeddings Improve Instruction Finetuning

iclr 2024 reviewer 评分 5666 1 论文思路 论文的原理很简单&#xff1a;在finetune过程的词向量中引入一些均匀分布的噪声即可明显地提升模型的表现 2 方法评估

CUDA与cuDNN详细安装教程(最新)

写在前面&#xff1a; 在深度学习中&#xff0c;我们常常要对图像数据进行处理和计算&#xff0c;而处理器CPU因为需要处理的事情多&#xff0c;并不能满足我们对图像处理和计算速度的要求&#xff0c;显卡GPU就是来帮助CPU来解决这个问题的&#xff0c;GPU特别擅长处理图像数…

2024 年“认证杯”数学中国数学建模网络挑战赛

题目 C题 云中的海盐 巴黎气候协定提出的目标是&#xff1a;在 2100 年前&#xff0c;把全球平均气温相对于工业 革命以前的气温升幅控制在不超过 2 摄氏度的水平&#xff0c;并为 1.5 摄氏度而努力。 但事实上&#xff0c;许多之前的研究已经指出&#xff0c;全球的碳排放以及…

【面试八股总结】进程(二)

参考资料 &#xff1a;小林Coding、阿秀、代码随想录 一、进程调度 当⼀个进程的状态发⽣改变时&#xff0c;操作系统需要考虑是否要换⼀个进程执行&#xff0c;这就需要⽤到“进程调度算法”。 1. 调度目标 不同的调度算法具有不同的特性&#xff0c;因为使用以下标准比较…

Codewars:找到奇数 int

Codewars&#xff1a;找到奇数 int 给定一个数组&#xff0c;找到出现次数为奇数的整数。在数组中&#xff0c;总会有一个整数出现奇数次&#xff0c;尽管其他数字可能会出现多次。 例子&#xff1a; 输入&#xff1a;findOdd([20, 1, 1, 2, 2]) 输出&#xff1a;20输入&…