开发中我们经常会用到 Spring Boot 的事务注解,为含有多种操作的方法添加事务,做到如果某一个环节出错,全部回滚的效果。但是在开发中可能会因为不了解事务机制,而导致我们的方法使用了 @Transactional 注解但是没有生效的情况,下面就把这几种不能生效的情况整理一下。

文章目录

  • 一、非public方法(动态代理限制)
  • 二、自调用问题(类内部方法调用,不走代理)
  • 三、异常类型不匹配(默认只回滚RuntimeException)
  • 四、多线程切换(事务连接绑定ThreadLocal)
  • 五、错误传播行为(如:PROPAGATION_NOT_SUPPORTED挂起事务)
  • 六、总结

一、非public方法(动态代理限制)

Spring 的事务管理本质上是通过 AOP 动态代理 实现的(JDK 动态代理或 CGLIB 代理)。

代理对象在调用目标方法时,会添加事务管理的逻辑(开启事务、提交/回滚事务)。

然而,动态代理只能代理 public 方法。

如果你将 @Transactional 注解放在 protectedprivate 或默认(包级私有)方法上,Spring 在创建代理时无法为这些方法添加事务增强逻辑。

当你通过代理对象调用这些非 public 方法时,事务相关的代码(如 beginTransaction(), commit(), rollback())不会被织入,因此事务管理完全失效。

所以,要确保所有需要事务管理的方法都是 public 的。这是 Spring AOP 代理机制的一个硬性限制。

二、自调用问题(类内部方法调用,不走代理)

这是 AOP 代理机制带来的另一个典型问题。假设一个 Service 类中有两个方法:

  • methodA():没有 @Transactional 注解。
  • methodB():有 @Transactional 注解。

如果你在 methodA() 内部直接调用 this.methodB(),那么你调用的是 Service 类本身的 methodA()this 指向目标对象本身)。methodA() 内部调用 this.methodB(),是目标对象内部的方法调用
这个调用完全不经过为该 Service 类生成的代理对象。

因为调用 methodB() 没有经过代理对象,所以代理对象上附加的事务拦截逻辑根本不会被执行。methodB() 虽然标注了 @Transactional,但在此次调用中完全失效。


解决方案有以下几种:推荐重构代码。

方案一:注入自身代理对象

开启 exposeProxy:在配置类(如 @SpringBootApplication 主类)上添加 @EnableAspectJAutoProxy(exposeProxy = true)

在需要自调用事务方法的地方获取代理对象:

((YourServiceClass) AopContext.currentProxy()).methodB();

AopContext.currentProxy() 获取到当前方法执行上下文中的代理对象(即被 Spring AOP 增强过的对象),通过这个代理对象调用 methodB(),就会走代理逻辑,事务拦截器生效。

这种方式不常用,会有缺点,引入了 Spring AOP 特定 API (AopContext),增加了代码耦合度。

方案二:重构代码(推荐)

将需要事务管理的业务逻辑 methodB() 抽取到另一个独立的 Bean(如另一个 Service)中。然后在原来的 methodA() 中注入并使用这个新的 Bean 来调用 methodB()。这样调用自然通过代理对象进行。

这是更符合设计原则(单一职责、依赖注入)的做法,避免了自调用问题,也降低了耦合。

方案三:使用 ApplicationContext 获取 Bean

在类中注入 ApplicationContext,然后通过 ctx.getBean(YourServiceClass.class).methodB() 来调用。这样获取到的是代理 Bean,调用会走代理。

代码略显繁琐,并且也需要依赖 Spring 容器。

三、异常类型不匹配(默认只回滚RuntimeException)

@Transactional 注解的 rollbackFor 属性默认值是 RuntimeExceptionError

  • 当方法抛出 RuntimeException 或其子类(如 NullPointerException, IllegalArgumentException)时,Spring 会回滚事务。
  • 当方法抛出检查型异常(如 IOException, SQLException)时,Spring 默认会提交事务!

如果你在一个事务方法中抛出了自定义的业务异常(继承自 Exception 而非 RuntimeException),或者抛出了其他检查型异常,并且没有显式配置 rollbackFor,那么即使业务逻辑出错抛出了异常,Spring 也会正常提交事务,导致数据不一致。

这时,我们要显式指定 rollbackFor:在 @Transactional 注解中明确声明哪些异常需要触发回滚。

// 回滚所有 Exception 和自定义异常
@Transactional(rollbackFor = {Exception.class, YourCustomBusinessException.class}) 
public void transactionalMethod() throws Exception { ... }

或者修改默认行为(谨慎):虽然不推荐,但可以通过修改 Spring 的全局事务管理器配置来改变默认的回滚异常类型(例如改为回滚所有 Throwable)。

但这样做风险较大,可能回滚不应该回滚的异常(如 OutOfMemoryError)。

最佳实践还是根据具体业务在注解上显式配置 rollbackFornoRollbackFor

四、多线程切换(事务连接绑定ThreadLocal)

Spring 的事务管理核心是将数据库连接(Connection)绑定到当前执行线程(Thread)的 ThreadLocal 变量上。

一个事务从开始(beginTransaction)到提交/回滚(commit/rollback)期间,所有数据库操作都使用这个绑定在当前线程 ThreadLocal 上的同一个 Connection,以此保证 ACID 特性。

如果你在一个事务方法内部启动了一个新线程(new Thread() 或者使用线程池(如 @Async)执行数据库操作,会出现以下情况:

  • 新线程拥有自己独立的 ThreadLocal 存储。
  • 新线程无法访问到原始事务线程绑定的 Connection 对象。
  • 新线程中的数据库操作会从连接池获取一个新的、独立的 Connection
  • 这个新 Connection 不参与原始事务,其操作会在自身 autoCommit 模式下立即执行(通常是自动提交),与原始事务完全隔离。

新线程中的数据库操作成功与否不影响原始事务的提交或回滚,反之亦然。破坏了事务的原子性(Atomicity)。原始事务回滚不会回滚新线程中的操作;新线程操作失败也不会导致原始事务回滚。

解决方案:处理多线程下的数据一致性非常复杂,没有银弹:

  • **避免在事务方法内开启异步线程执行 DB 操作:**这是最根本的预防措施。将需要在同一事务中完成的操作放在同一个线程内执行。
  • 编程式事务管理: 在新线程内部,使用 TransactionTemplate 手动管理事务边界。但这只是让新线程内部操作具有事务性,无法与原始线程的事务合并成一个原子事务。
  • **分布式事务:**如果业务强要求跨线程的 ACID,可能需要引入分布式事务管理器(如 Seata, Atomikos)来处理这种跨 资源(不同线程可视为不同资源管理者)的场景,但代价高昂且复杂。
  • 设计补偿机制: 在业务层设计最终一致性方案(如 Saga 模式),通过记录操作日志、发送消息、定时任务补偿等方式,在异步操作失败后尝试回滚或修正原始事务已提交的操作。这是更常见的处理异步事务一致性的实践。

五、错误传播行为(如:PROPAGATION_NOT_SUPPORTED挂起事务)

@Transactionalpropagation 属性定义了当前方法的事务如何与已存在的事务进行交互。使用不当会导致事务行为不符合预期。

PROPAGATION_NOT_SUPPORTED 不支持事务。如果当前存在事务,则挂起(Suspend) 这个事务;然后以非事务方式执行当前方法。方法执行完毕后,之前挂起的事务恢复(Resume)。

假设方法 outer() 开启了一个事务(Propagation.REQUIRED),在其内部调用 inner() 方法,而 inner() 被标注为 @Transactional(propagation = Propagation.NOT_SUPPORTED),当执行到 inner() 时:

  1. 系统检测到当前存在 outer() 开启的事务。
  2. 根据 NOT_SUPPORTED 语义,挂起 outer() 的事务
  3. inner() 方法在无事务状态下执行(相当于 autoCommit=true)。
  4. inner() 方法执行完毕(无论成功失败,其操作已立即提交)。
  5. 恢复 outer() 的事务,继续执行 outer() 剩余代码。

结果是 inner() 方法中的数据库操作不受 outer() 事务控制。即使 outer() 最终因异常回滚,inner() 中已提交的操作不会被回滚!这通常不是开发者想要的效果,极易造成数据不一致。

其他易错传播行为:

  • PROPAGATION_NEVER 要求不能存在事务。如果调用者在一个事务中调用了标记为 NEVER 的方法,会直接抛出 IllegalTransactionStateException 异常。
  • PROPAGATION_SUPPORTS 如果当前存在事务,就加入该事务;如果没有,就以非事务方式执行。关键点在于非事务方式。如果方法中有多个操作且需要原子性,而外部又恰好没有事务,这些操作就会各自独立提交。
  • PROPAGATION_REQUIRES_NEW 总是开启一个全新的、独立的事务。会挂起外部事务(如果存在)。新事务的提交/回滚与外部事务互不影响。注意: 这虽然创建了新事务,但不同于自调用失效,它是有效的(通过代理调用)。它的陷阱在于开发者可能误以为新事务是外部事务的一部分,其实它们是独立的。

解决方案:

  • 深入理解传播行为: 务必清楚每种传播行为(REQUIRED, REQUIRES_NEW, SUPPORTS, MANDATORY, NOT_SUPPORTED, NEVER, NESTED)的精确语义。
  • 谨慎选择传播行为: 默认使用 Propagation.REQUIRED 通常能满足大多数场景(加入现有事务,没有则新建)。只有在有明确且充分理由时才使用其他传播行为。
  • 代码审查与测试: 对使用了非默认传播行为的代码进行重点审查,并通过单元测试、集成测试模拟各种调用链路,验证事务边界和回滚行为是否符合预期。特别注意跨方法、跨服务调用时的事务传播。

六、总结

Spring Boot 事务失效的核心原因通常围绕:

  • AOP 代理机制的限制(非 public、自调用)
  • 异常处理机制(默认回滚异常类型)
  • 资源绑定机制(ThreadLocal 导致多线程失效)
  • 配置错误(传播行为误用)

解决这些问题需要深入理解 Spring 事务管理的底层原理(代理、ThreadLocal、异常回滚规则、传播语义),并在编码和配置时保持谨慎,遵循最佳实践(如方法 public、避免自调用、显式指定 rollbackFor、理解传播行为、避免事务内跨线程操作 DB)。

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

#C语言——刷题攻略:牛客编程入门训练(四):运算

🌟菜鸟主页:晨非辰的主页 👀学习专栏:《C语言刷题合集》 💪学习阶段:C语言方向初学者 ⏳名言欣赏:"代码行数决定你的下限,算法思维决定你的上限。" 目录 1. BC25 牛牛买电…

阻抗分析中的软件解调计算

接上篇 重温无功功率测量-CSDN博客 已知被测阻抗两端电压与流过 通过两个ADC同步采集到。 激励频率10k, 采样率1M, 每周期100个点 关键是:采样率除以激励频率, 得是4的倍数... 所以ADC不能自由运行, 得用一个timer来触发. 因为要进行同相分量正交分量计算。 1:直…

ubuntu 镜像克隆

一、克隆 1、准备 一个u盘(制作启动盘) 一个移动固态硬盘(大于要克隆系统盘的1.2倍) 2、使用 rufus生成系统启动盘 (1)下载ubuntu iso 桌面版 https://cn.ubuntu.com/download (2&#x…

Axure下拉菜单:从基础交互到高保真元件库应用

在Web端产品设计中,下拉菜单(Dropdown Menu) 是用户与系统交互的核心组件之一,它通过隐藏次要选项、节省页面空间的方式,提升信息密度与操作效率。无论是基础下拉菜单、图标式下拉菜单,还是复杂的多级下拉菜…

复现YOLOV5+训练指定数据集

一、复现YOLOV5代码 1.github下载:https://github.com/MIPIT-Team/SSA-YOLO 2.配置环境:创建虚拟环境yolo5 conda create -n yolo5 python3.9 #对应文件夹下pip install -r requirements.txt报错:ERROR: pips dependency resolver does no…

Agents-SDK智能体开发[4]之集成MCP入门

文章目录说明一 Agents SDK接入MCP1.1 MCP技术回顾1.2 MCP基础实践流程1.2.1 天气查询服务器Server创建流程1.2.2 服务器依赖安装和代码编写1.2.3 环境配置文件1.2.4 客户端代码编写1.3 测试运行二 MCPAgents SDK基础调用2.1 weather_server.py2.2 client_agent.py2.3 运行测试…

Camera相机人脸识别系列专题分析之十九:MTK ISP6S平台FDNode传递三方FFD到APP流程解析

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: Camera相机人脸识别系列专题分析之十九:MTK平台FDNode传递三方FFD到APP流程解析 目录 一、背景 二、:OcamMeta传递FFD到APP 2.1:OcamMeta 2.2 :OcamMeta::process更新FFD 2.…

【实时Linux实战系列】构建实时监测与报警系统

在实时系统中,监测与报警系统是确保系统正常运行和及时响应异常情况的关键组件。实时监测与报警系统能够实时收集系统数据,分析关键事件,并在检测到异常时发出警报。这种系统广泛应用于工业自动化、医疗设备监控、网络安全等领域。掌握实时监…

PHP入门及数据类型

PHP数据类型 PHP标记 //HTML风格 <?phpecho "hello world"; ?> //简短风格 <?echo "hello world"; ?>数据类型 PHP 最初源于 Perl 语言&#xff0c;与 Perl 类似&#xff0c;PHP 对数据类型采取较为宽松的态度。PHP 规定&#xff0c;变量数…

沸点 | 嬴图参加世界人工智能大会

2025 WAIC于 7 月 26 日至 28 日在上海举行。大会展览面积突破 7 万平方米&#xff0c;800 余家企业参展。嬴图作为图数据库领域的领先企业&#xff0c;携前沿技术与创新应用精彩亮相。​大会期间&#xff0c;嬴图创始人兼CEO孙宇熙与来自全球的顶尖学者、企业代表共同探讨人工…

2. 字符设备驱动

一、设备号 1.1. 什么是设备号 设备号是用来标记一类设备以及区分这类设备中具体个体的一组号码。 设备号由主设备号和次设备号组成。主设备号的作用为标记一类设备、用于标识设备驱动程序,而次设备号的作用是为了区分这类设备中的具体个体设备及用于标识同一驱动程序下的具…

uboot armv8 启动流程之 linker script

section 详细说明.text按如下顺序&#xff0c;中断向量表vectors, 启动入口代码start.o,普通text, glue &#xff08;arm thumb2 相互调用时自动生成的代码&#xff09;*(.vectors)CPUDIR/start.o (.text*)*(.text*)*(.glue*)__image_copy_start 标记为text 段入口&#xff0c;…

xxljob总结

XXL-Job 支持多种任务类型&#xff0c;以下是常见任务类型的示例 Demo&#xff0c;包含核心配置和代码片段&#xff0c;帮助快速理解用法&#xff1a;一、Bean模式任务&#xff08;最常用&#xff09;通过注解 XxlJob 定义任务方法&#xff0c;直接在 Spring 容器中管理&…

Python包安全工程实践:构建安全可靠的Python生态系统

在现代计算环境中&#xff0c;性能往往是Python包成功的关键因素。本文将深入探讨Python包的性能优化技术&#xff0c;包括并发编程模型、性能分析工具、内存优化策略以及原生代码集成等高级主题&#xff0c;帮助你构建高性能的Python组件。1. 性能分析基础1.1 性能分析工具矩阵…

kubernetes基础知识

个人博客站—运维鹿: http://www.kervin24.top CSDN博客—做个超努力的小奚&#xff1a; https://blog.csdn.net/qq_52914969?typeblog一、kubernetes介绍Kubernetes本质是一组服务器集群&#xff0c;它可以在集群的每个节点上运行特定的程序&#xff0c;来对节点中的容器进行…

winntsetup安装驱动和光驱安装F6功能一样----NT5.2.3790源代码分析

D:\drv>dir驱动器 D 中的卷是 新加卷卷的序列号是 443D-D64BD:\drv 的目录2025-08-03 23:57 <DIR> . 2025-08-03 23:57 <DIR> .. 2008-05-27 10:01 119,068 yk51x86.cat 2008-05-20 10:01 969,380 yk51x86.inf…

Web 开发 11

今天完成了workshop2&#xff0c;进度有点慢&#xff0c;但是记录一下极为愚蠢的一轮轮问答和思考~&#xff01;&#xff08;还是有点成就感的&#xff09;ps&#xff1a;【】内为我的提问1 导入语句&#xff08;ES6 模块导入语法&#xff09;【import CatHappiness from "…

写作路上的迷茫与突破

曾经&#xff0c;我也是那个在写作面前踌躇不前的人。每次提笔&#xff0c;满心都是“我写不好”“我没什么可写的”“我达不到别人的高度”……这些念头像藤蔓一样&#xff0c;紧紧缠绕着我&#xff0c;让我寸步难行。我看着群里的小伙伴们一个个妙笔生花&#xff0c;自己却只…

23 Active Directory攻击与防护策略解析

引言 Active Directory&#xff08;AD&#xff09;是企业IT环境中用户认证、访问控制和身份管理的核心。因其掌握整个网络的"钥匙"&#xff0c;AD常成为攻击者的首要目标。 从凭证转储到隐蔽侦察&#xff0c;攻击者通过多种手段控制AD。无论您是网络安全分析师、红…

【内容规范】关于标题中【】标记的使用说明

【内容规范】关于标题中【】标记的使用说明 在信息爆炸的时代&#xff0c;如何让内容更易识别、更具条理性&#xff0c;成为内容创作者和平台运营者共同关注的问题。标题中【】标记的使用&#xff0c;正是在这种需求下形成的一种实用规范。 这种规范的核心作用在于建立统一的内…