《后端程序猿 · Spring事务失效场景》

news2024/9/30 21:51:37

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗

🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,欢迎多多交流。👍

文章目录

    • 写在前面的话
    • Spring 事务失效场景
      • 方法访问修饰符非public
      • 方法使用 static
      • 方法使用 final
      • 同类方法自调用(★)
      • 异步调用场景(★)
      • 没有被 Spring 管理
      • 异常类型不符合
      • 传播行为不当
      • 其他场景
    • 扩展 · 部分源码分析
    • 总结陈词

写在前面的话

Spring 事务管理是通过 AOP(面向切面编程)实现的,提供了声明式事务管理的能力。尽管 Spring 提供了强大的事务管理功能,但在某些情况下,事务可能会失效。

推荐文章

《故障复盘 · 记一次事务用法错误导致的大量锁表问题》


Spring 事务失效场景

方法访问修饰符非public

场景描述:

Java 的访问权限主要是:private、default、protected、public,它们的权限则是依次变大。

如果事务方法的访问修饰符是 protected、private 或 default,Spring AOP 代理无法拦截这些方法。

逻辑分析:

AbstractFallbackTransactionAttributeSource类 的 computeTransactionAttribute 方法中有个判断,如果目标方法不是public,则TransactionAttribute返回null,即不支持事务。

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

    //省略部分代码
}

解决方案:

将事务方法的访问修饰符设置为 public。


方法使用 static

场景描述:

如果事务方法被声明为 static,则 Spring AOP 代理无法拦截该方法。

逻辑分析:

静态方法属于类本身,而不是类的实例。Spring AOP 代理是基于实例的,因此无法对静态方法进行代理。

解决方案:

避免将事务方法声明为 static。


方法使用 final

场景描述:

在 Java 中,final 关键字用于修饰类、方法和变量,表示这些元素不能被修改或重写。

如果事务方法被声明为 final,则 Spring AOP 代理无法拦截该方法。

逻辑分析:

final 方法在编译时会被优化,无法被子类重写。CGLIB 代理是通过子类化来实现的,因此无法代理 final 方法。

例如,CGLIB 代理的实现中,如果方法被标记为 final,则在生成代理类时会跳过这些方法:

public class Enhancer {
    protected void generateMethod(ClassGenerator gen, Method method) {
        // 跳过 final 方法
        if (Modifier.isFinal(method.getModifiers())) {
            return; 
        }
    }
}

解决方案:

避免将事务方法声明为 final。

Tips:上面几个错误很明显,IDEA也会给出相应提示,应该不容易会出现。


同类方法自调用(★)

场景描述:

当一个类中的方法 A 调用同一类中的方法 B 时,如果方法 B 上有事务注解(如 @Transactional),而方法 A 没有,事务可能会失效。

逻辑分析:

如事务注解 @Transactional 是基于动态代理实现的,Spring 采用动态代理(AOP)实现对 bean 的管理和切片,它为我们的每个 class 生成一个代理对象。只有在代理对象之间进行调用时,可以触发切面逻辑。而在同一个 class 中,方法B调用方法A,调用的是原对象的方法,而不通过代理对象,所以 Spring 无法切到这次调用,也就无法通过注解保证事务性了。

友情提示:@Aspectj、@Async,@Transational、@Cacheable 等注解都是基于AOP 实现的,AOP是基于动态代理实现,存在问题差不多。

原理补充:

由于 Spring AOP 采用了动态代理实现,在Spring 容器中的bean(也就是目标对象)会被代理对象代替,代理对象里加入了我们需要的增强逻辑,当调用代理对象的方法时,目标对象的方法就会被拦截。通过调用代理对象的A方法,在其内部会经过切面增强,然后方法被发射到目标对象,在目标对象上执行原有逻辑,如果在原有逻辑中(同类)嵌套调用了B方法,则此时B方法并没有被进行切面增强,因为此时它已经在目标对象内部。而解决方案很好地说明了,将嵌套方法发射到代理对象,这样就完成了切面增强。

解决方案1:

迁移方法,把业务逻辑抽离到另外一个Service,然后正常注入,调用。

这种方式最稳妥,但是改造量可能偏大。

解决方案2:

获取本对象的代理对象,再进行调用,有两种方式:

1、从Bean工厂获取,注入自身,或者通过getBean方式;

2、使用 AopContext.currentProxy() 方式;

Tips:注入自身的问题,由于这种写法基于Spring的三级缓存不会导致循环依赖的问题出现。

解决方案3:

使用编程式事务,自主控制事务的提交和回滚。


异步调用场景(★)

场景描述:

如果你将 @Async 和 @Transactional 注解放在同一个方法上,通常会导致事务失效。这是因为 @Async 注解会导致该方法在一个新的线程中执行,而 Spring 的事务管理是基于代理的,通常只在同一线程中有效。

逻辑分析:

1、代理机制:Spring 的事务管理是通过 AOP(面向切面编程)实现的,通常使用 JDK 动态代理或 CGLIB 代理。当你在一个方法上使用 @Transactional 注解时,Spring 会创建一个代理对象来管理事务。

2、异步执行:当你在同一个方法上使用 @Async 注解时,Spring 会将该方法的调用转发到一个新的线程中执行。由于事务是绑定到调用线程的,而不是代理对象的,因此在新的线程中,事务不会生效。

解决方案1:

将事务和异步调用分开:将 @Transactional 注解放在一个单独的方法上,然后在该方法中调用带有 @Async 注解的方法。

解决方案2:

如果一定要先用异步逻辑,那么可以在异步逻辑中使用编程式事务。

补充:为什么同时加 @Async 和 @Transactional 时,异步生效而不是事务生效?

这主要是因为 Spring 的事务管理机制 和 异步执行机制 的工作原理不同。

1、事务管理机制:Spring 的事务管理是基于 代理 的。当一个方法被 @Transactional 注解标记时,Spring 会为该方法生成一个代理对象。这个代理对象会拦截方法调用,并在方法执行前后进行事务管理操作(例如,开始事务、提交事务、回滚事务)。

2、异步执行机制:Spring 的异步执行机制是基于 线程池 的。当一个方法被 @Async 注解标记时,Spring 会将该方法提交到一个线程池中执行。线程池会创建一个新的线程来执行该方法,而这个新的线程与当前线程是独立的。

当 @Async 和 @Transactional 同时存在时,Spring 会优先处理 @Async 注解。这意味着方法会被提交到线程池中执行,而不是被代理对象拦截。由于事务管理是基于代理的,因此在异步线程中,事务管理机制无法生效。简单来说,事务管理需要在同一个线程中进行,而异步执行会创建新的线程,导致事务管理无法生效。

总之,@Async 注解会将方法提交到线程池中执行,而 @Transactional 注解会为方法生成代理对象。Spring 会优先处理 @Async 注解,因此事务管理机制无法在异步线程中生效。


没有被 Spring 管理

场景描述:

如果一个被 @Transactional 注解的方法被一个非 Spring 管理的类调用,事务也会失效。

逻辑分析:

这个好像不用多说了,SpringAOP都不会触发。

直接检查一下是不是Bean扫描路径不对等问题。


异常类型不符合

场景描述:

默认情况下,Spring 只会对未检查异常(RuntimeException)进行回滚。如果抛出的是检查异常(Exception),事务不会自动回滚,除非在 @Transactional 注解中指定。

逻辑分析:

@Transactional
public void createUser() {
    // 业务逻辑
    throw new IOException(); // 事务不会回滚
}

@Override
public boolean rollbackOn(Throwable ex) {
	return (ex instanceof RuntimeException || ex instanceof Error);
}

解决方案:

按需调整,例如:@Transactional(rollbackFor = Exception.class)


传播行为不当

场景描述:

事务的传播行为决定了方法调用时如何处理事务。如果传播行为设置不当,可能导致事务失效。例如,使用 Propagation.REQUIRES_NEW 会导致新事务的创建,而不是在现有事务中执行。

逻辑分析:

这种情况不能说事务失效,只能说要按自己的需要处理。


其他场景

1、数据库不支持事务(较少);

2、项目没有开启事务能力(较少);

3、开发捕获了异常导致没回滚(常见);

4、未完待续。。。


扩展 · 部分源码分析

模拟一个事务方法调用,当该方法被调用时,Spring AOP 会拦截这个调用,进入下面的流程。

Step1、事务拦截器触发

参考:TransactionInterceptor#invoke

说明:TransactionInterceptor 会调用 invoke 方法,检查方法上是否有 @Transactional 注解。

Step2、获取事务属性

参考:AbstractFallbackTransactionAttributeSource#getTransactionAttribute

说明:getTransactionAttribute 方法来获取事务属性,这里还涉及一个缓存机制,先不管。

Step3、开启事务

参考:TransactionAspectSupport#invokeWithinTransaction

说明:通过 PlatformTransactionManager 的 getTransaction 方法开始一个新事务,这会创建一个 TransactionStatus 对象,表示当前事务的状态。

Step4、执行目标方法

参考:retVal = invocation.proceedWithInvocation()

说明:执行目标方法,操作数据库。

Step5、提交或回滚事务

如果目标方法执行成功,TransactionInterceptor 会调用 commitTransactionAfterReturning/commit 方法提交事务。

如果在执行过程中抛出异常,TransactionInterceptor 会调用 completeTransactionAfterThrowing/rollback 方法回滚事务。

Step6、结束事务

参考:cleanupTransactionInfo

说明:TransactionInterceptor 会清理事务状态,结束事务。


总结陈词

此篇文章介绍了 Spring 事务的常见失效场景,仅供学习参考。

通过本篇文章的分析,可以看到,Spring事务失效的原因,大半部分和SpringAOP原理有关系,如果某些因素导致AOP无法生效或代理类无法操作,则事务随之失效了。从源码分析过程中,也能找到部分事务失效场景对应的代码。

Spring 的事务能讨论的知识点还很多,后续有机会再进行专题补充,

💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。

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

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

相关文章

如何使用ssm实现钢铁集团公司安全管理系统的构建与实现

TOC ssm748钢铁集团公司安全管理系统的构建与实现jsp 研究背景与现状 时代的进步使人们的生活实现了部分自动化&#xff0c;由最初的全手动办公已转向手动自动相结合的方式。比如各种办公系统、智能电子电器的出现&#xff0c;都为人们生活的享受提供帮助。采用新型的自动化…

SpringBoot教程(三十一) | SpringBoot生成Docker镜像包

SpringBoot教程&#xff08;三十&#xff09; | SpringBoot生成Docker镜像包 前提方式一&#xff1a;spring-boot-maven-plugin 方式方式二&#xff1a;Dockfile 方式&#xff08;推荐&#xff09; 前提 如果你在 Windows 上&#xff0c;确保 Docker Desktop 已经启动并正在运…

Java常用三类定时器快速入手指南

文章目录 Java常用三类定时器快速入手指南一、序言二&#xff0c;Timer相关1、概念2、Timer类3、TimerTask类4、ScheduleExecutorService接口 三&#xff0c;Scheduled相关1、配置1.1 SpringMVC配置1.2 SpringBoot配置&#xff08;1&#xff09;单线程&#xff08;2&#xff09…

python 如何引用变量

在字符串中引入变量有三种方法&#xff1a; 1、 连字符 name zhangsan print(my name is name) 结果为 my name is zhangsan 2、% 字符 name zhangsan age 25 price 4500.225 print(my name is %s%(name)) print(i am %d%(age) years old) print(my price is %f%(pric…

【数字图像处理】小白也能懂,最浅显方式手撕直方图均衡化(附python实现)

文章目录 1 概念2 原理2.1 数学原理 3 python代码实现4 测试效果5 结论 1 概念 直方图均衡化&#xff0c;同伽马变换一样&#xff0c;也是增强图像对比度的一种工具。区别在于&#xff0c;直方图均衡化是一种自适应的工具&#xff0c;即自动工具。也就是说&#xff0c;我们只需…

使用RestTemplate调用EMQX API查询MQTT客户端列表信息

项目中集成mqtt客户端查询功能&#xff0c;使用到了EMQX api-v5&#xff0c;具体步骤&#xff1a; 一、准备工作 首先在EMQX dashboard中添加API 密钥 填写密钥名称&#xff0c;点击确定&#xff0c;会生成API Key和Secret Key&#xff0c;保存起来备用。 二、配置文件 在…

SUP-NeRF-ECCV2024数据集: 单目3D对象重建的新突破

2024-09-25&#xff0c;由Bosch Research North America和Michigan State University联合发布的SUP-NeRF&#xff0c;是一个基于单目图像进行3D对象重建的新型方法。一个无缝集成姿态估计和物体重建的统一网格。 ECCV&#xff1a;欧洲计算机视觉会议的缩写&#xff0c;它是计算…

如何使用ssm实现科技银行业务管理系统+vue

TOC ssm743科技银行业务管理系统vue 第一章 绪论 1.1 研究背景 在现在社会&#xff0c;对于信息处理方面&#xff0c;是有很高的要求的&#xff0c;因为信息的产生是无时无刻的&#xff0c;并且信息产生的数量是呈几何形式的增加&#xff0c;而增加的信息如何存储以及短时间…

移除元素

移除元素 题目链接&#xff1a;移除元素 示例 1&#xff1a; 输入&#xff1a;nums [3,2,2,3], val 3 输出&#xff1a;2, nums [2,2,_,_] 解释&#xff1a;你的函数函数应该返回 k 2, 并且 nums 中的前两个元素均为 2。 你在返回的 k 个元素之外留下了什么并不重要&…

URL从输入到⻚面显示的过程(详细版)

URL从输入到⻚面显示的过程&#xff08;详细版&#xff09; 浏览器中输入网址 DNS 解析域名得到 IP 地址 DNS 解析首先会从你的浏览器的缓存中去寻找是否有这个网址对应的 IP 地址&#xff0c;如果没有就向OS系统的 DNS 缓存中寻找&#xff0c;如果没有就是路由器的 DNS 缓存&…

C++之 友元重载 以及最常用的几种友元函数

在之前的友元中就曾经讲过&#xff0c;我们为了去访问修改私有成员中的数据时&#xff0c;只能通过公有的办法去进行访问操作&#xff0c;非常的局限。所以C引用了友元函数&#xff0c;只要加上friend关键字&#xff0c;C的这个类&#xff0c;会自动把这个函数的权限拉到类内&a…

无水印短视频素材下载网站有哪些?十个高清无水印视频素材网站分享

你知道怎么下载无水印视频素材吗&#xff1f;今天小编就给大家推荐十个高清无水印视频素材下载的网站&#xff0c;如果你也是苦于下载高清无水印的短视频素材&#xff0c;赶紧来看看吧&#xff5e; 1. 稻虎网 首推的是稻虎网。这个网站简直就是短视频创作者的宝库。无论你需要…

编程魔法:基于LLM的AI function开发,如何实现高效数据生成?

基于大语言模型&#xff08;LLM&#xff09;的AI function开发&#xff0c;简直就是现代编程界的“魔法棒”&#xff01; 你好&#xff0c;我是三桥君 最近三桥君有个任务&#xff0c;需要造一些测试数据&#xff0c;比如姓名、手机号、银行卡号、邮箱啥的&#xff0c;用来做测…

每日OJ题_牛客_添加逗号_模拟_C++_Java

目录 牛客_添加逗号_模拟 题目解析 C代码1 C代码2 Java代码 牛客_添加逗号_模拟 添加逗号_牛客题霸_牛客网 题目解析 读取输入&#xff1a;读取一行字符串。分割字符串&#xff1a;使用空格将字符串分割成单词数组。拼接字符串&#xff1a;将单词数组中的每个单词用逗号…

群晖安装Gitea(代码托管工具)

一、Gitea介绍 Gitea 是一款开源的轻量级代码托管平台,可以为团队和开发者提供了一个易于部署、运行快速、使用体验良好的自建 Git 服务。相比于其它自部署代码托管平台,Gitea 的设计更加轻量,对系统资源的占用相对较少,能够在较低配置的服务器上流畅运行。相比于其他代码…

嘉楠科技AI芯片K230-初探

勘智K230 介绍入门购买开发板 安装开机开发学习点亮第1个LED点亮屏幕预览摄像头代码离线运行 在线训练平台 参考 介绍 K230芯片是嘉楠科技 Kendryte系列AIoT芯片中的最新一代SoC产品。该芯片采用全新的多异构单元加速计算架构&#xff0c;集成了2个RISC-V高能效计算核心&#x…

Spring系列 BeanPostProcessor

文章目录 BeanPostProcessor注册时机执行时机 InstantiationAwareBeanPostProcessorSmartInstantiationAwareBeanPostProcessor 本文源码基于spring-beans-5.3.31 参考&#xff1a;https://docs.spring.io/spring-framework/reference/core/beans/factory-extension.html#beans…

【ASE】第四课_高亮显示效果(手动切换)

今天我们一起来学习ASE插件&#xff0c;希望各位点个关注&#xff0c;一起跟随我的步伐 今天我们来学习高亮的效果。 思路&#xff1a; 1.添加纹理贴图和法线贴图&#xff0c;环境光遮挡贴图 2.添加高亮的参数&#xff0c;并设置 3.手搓一个边缘光,通过高亮参数调节 4.将模…

微信小程序——音乐播放器

目的 掌握swiper组件、scroll-view组件的使用掌握image组件的使用掌握音频API的使用掌握slider组件的使用 内容 了音乐小程序项目的完整开发流程&#xff0c;其开发步骤包括页面结构的分析、样式的设计、组件的运用等。通过本章的学习&#xff0c;读者能够掌握小程序的基本交…

聚星文社——绘唐科技有什么区别!

聚星文社和绘唐科技是两个不同的公司&#xff0c;有一些区别。下面是它们的一些区别&#xff1a; 绘唐科技——聚星文社https://iimenvrieak.feishu.cn/docx/ZhRNdEWT6oGdCwxdhOPcdds7nof 行业领域&#xff1a;聚星文社主要从事文化娱乐行业&#xff0c;包括出版、影视制作等&…