当前位置: 首页 > article >正文

第五篇:Spring事务管理——@Transactional的底层实现与失效场景

前言在前面的文章中我们拆解了Spring AOP的底层原理——动态代理和切面编程。现在我们来看AOP最经典的应用事务管理。你每天用着Transactional往Service方法上一加事务就自动开启了。但面试中事务是Spring最深的水区之一“Transactional是怎么实现的它和AOP是什么关系”“为什么同一个类中方法互相调用事务会失效”“事务传播行为有哪些REQUIRES_NEW和NESTED有什么区别”“什么情况下Transactional会失效至少说出三种场景。”这些问题考察的不是会不会加注解而是理不理解事务拦截的底层机制。本文从事务拦截链出发逐层拆解Spring事务管理的实现原理和经典失效场景。本文核心问题Transactional是怎么实现的TransactionInterceptor做了什么事务传播行为有哪些REQUIRED、REQUIRES_NEW、NESTED分别适用什么场景为什么同一个类中方法互相调用事务会失效Transactional注解在哪些场景下会失效至少说出四种失效场景及其根本原因。编程式事务和声明式事务各有什么优劣为什么Transactional是默认选择Spring事务和数据库事务是什么关系Spring在这里加了什么读完本文你将对Spring事务管理拥有从实现原理到避坑指南的完整理解。一、Transactional是怎么实现的——TransactionInterceptor疑问加上Transactional注解方法就能自动开启事务。Spring是怎么做到的回答和AOP一样——Spring为标注了Transactional的Bean创建代理对象在代理对象中由TransactionInterceptor拦截方法调用在方法前后管理事务。1.1 事务拦截的本质你写的Service Transactional public void createOrder(Order order) { orderMapper.insert(order); // ← 直接写数据库 } 实际运行时AOP代理 TransactionInterceptor.invoke(proxy, method, args): transactionManager.begin() // 开启事务 try: method.invoke(target, args) // 调用你的createOrder方法 transactionManager.commit() // 提交事务 catch: transactionManager.rollback() // 回滚事务 throw关键是TransactionInterceptor——它在代理对象中拦截了Transactional注解的方法把事务管理逻辑开启、提交、回滚包裹在业务方法前后。业务方法内部不需要写任何事务代码Spring通过AOP把事务逻辑从业务代码中完全剥离。1.2 事务拦截的触发条件Spring根据以下规则从容器管理的Bean中筛选出需要被事务拦截的类或方法类或方法上标注了Transactional如果是类级别标注则类中所有public方法都受事务管理如果是方法级别标注只有标注的方法受事务管理类中未标注的方法不受影响1.3 事务的创建过程TransactionInterceptor获取事务时核心参数包括事务传播行为、隔离级别、超时时间、只读标志和回滚规则。这些参数从Transactional注解中读取传递给事务管理器。事务管理器根据传播行为判断是新建事务还是加入已有事务然后设置隔离级别和超时控制最终返回事务状态对象。整个创建过程在TransactionInterceptor内部完成业务代码完全无感知。二、事务传播行为——REQUIRED、REQUIRES_NEW、NESTED的区别疑问事务传播行为是什么REQUIRED和REQUIRES_NEW有什么区别回答传播行为定义了一个事务方法被另一个事务方法调用时事务应该如何传播——是加入调用方的事务还是新建一个独立的事务。2.1 七种传播行为传播行为含义调用方有事务调用方无事务REQUIRED默认需要事务加入已有事务新建事务REQUIRES_NEW需要新事务挂起原事务新建独立事务新建事务NESTED嵌套事务创建保存点Savepoint新建事务SUPPORTS支持事务加入已有事务无事务执行NOT_SUPPORTED不支持事务挂起原事务无事务执行无事务执行MANDATORY强制需要事务加入已有事务抛异常NEVER不允许事务抛异常无事务执行2.2 REQUIRED vs REQUIRES_NEW场景订单创建成功时需要记录一条操作日志。创建订单失败时日志不能因为事务回滚而丢失。 REQUIRED错误方案 createOrder() { // 外层事务A orderMapper.insert(); // 写订单 logService.log(); // 写日志——使用REQUIRED加入外层事务 } 如果log()抛异常 → 整个事务A回滚 → 订单插入和日志插入都回滚 → 错误 REQUIRES_NEW正确方案 createOrder() { // 外层事务A orderMapper.insert(); // 写订单 logService.log(); // 写日志——使用REQUIRES_NEW挂起事务A新建事务B } 如果log()抛异常 → 事务B回滚事务A不受影响 → 订单创建成功日志丢失 如果createOrder()抛异常 → 事务A回滚事务B已提交 → 订单回滚日志保留REQUIRED和REQUIRES_NEW的本质区别REQUIRED让所有操作共享同一个事务同生共死REQUIRES_NEW把内层操作从外层事务中隔离出来独立提交或回滚。后者适合日志记录、通知发送等无论主流程成功与否都应记录的辅助操作。2.3 REQUIRES_NEW vs NESTEDNESTED和REQUIRES_NEW都会开启新事务但隔离方式不同NESTED在已有事务中使用保存点Savepoint内层回滚时可以回滚到保存点而不影响外层事务REQUIRES_NEW则是完全独立的新事务直接挂起外层事务。NESTED依赖JDBC的Savepoint机制仅支持JDBC且需要特定数据库支持如MySQL的InnoDB引擎。REQUIRES_NEW没有这个限制任何支持事务的环境都能使用但会占用额外的数据库连接。三、Transactional失效的四种经典场景疑问明明加了Transactional为什么事务没有生效回答Transactional依赖AOP代理来实现所有绕过代理的调用都会导致事务失效。3.1 失效场景一自调用——同一个类中方法互相调用ServicepublicclassOrderService{TransactionalpublicvoidcreateOrder(Orderorder){orderMapper.insert(order);// 有事务}publicvoidbatchCreate(ListOrderorders){for(Orderorder:orders){this.createOrder(order);// ← 事务失效}}}为什么失效this.createOrder()调用的是原始对象的方法this指针完全绕过了Spring生成的代理对象。事务拦截器在代理对象中生效而this引用指向的是原始对象——事务拦截器根本没有机会拦截这次调用。batchCreate本身没有事务它通过this调用的createOrder也不会有事务。解决方案将createOrder方法移到另一个Service中从容器中注入那个Service后调用或者通过AopContext.currentProxy()获取当前类的代理对象再调用自己的方法。3.2 失效场景二非public方法ServicepublicclassOrderService{TransactionalprivatevoidcreateOrderInternal(Orderorder){// ← private方法事务失效orderMapper.insert(order);}}为什么失效Spring AOP默认使用CGLIB动态代理——CGLIB通过继承目标类生成子类在子类中重写父类方法并插入拦截逻辑。private方法无法被继承、无法被重写——CGLIB对它无能为力。同理final方法也无法被重写事务同样不会生效。3.3 失效场景三异常类型不匹配TransactionalpublicvoidcreateOrder(Orderorder){try{orderMapper.insert(order);}catch(Exceptione){log.error(创建订单失败,e);// 吞掉异常不抛出}}Spring默认只在遇到RuntimeException和Error时回滚事务。CheckedException如IOException、SQLException默认不回滚除非通过Transactional(rollbackFor Exception.class)显式指定。而这里异常被try-catch吞掉后不仅默认的回滚条件没有触发事务拦截器也无从得知异常发生——它看到方法正常返回了直接提交了事务。3.4 失效场景四数据库引擎不支持事务CREATETABLEtb_order(idBIGINTPRIMARYKEY,...)ENGINEMyISAM;-- MyISAM不支持事务MyISAM引擎不支持事务——Transactional加了也没用因为底层数据库根本不支持事务的ACID特性。Spring事务管理依赖数据库的事务能力引擎不支持则Spring的一切事务逻辑都是空转。确保使用InnoDB引擎MySQL 5.5默认。3.5 失效场景速查表失效场景根本原因解决方案自调用this调用绕过了AOP代理抽到另一个Service中或通过AopContext.currentProxy()获取代理对象非public方法CGLIB无法重写私有/受保护方法改为public方法异常被吞掉事务拦截器没有感知到异常发生抛出异常让拦截器捕获或用TransactionAspectSupport手动回滚异常类型不匹配默认只对RuntimeException和Error触发回滚Transactional(rollbackFor Exception.class)数据库引擎不支持底层根本不支持事务使用InnoDB引擎四、编程式事务 vs 声明式事务疑问既然Transactional这么好用为什么还需要编程式事务回答Transactional适合大多数场景——事务范围和方法边界一致。但当需要在方法内部手动控制事务边界时编程式事务更灵活。4.1 声明式事务TransactionalTransactionalpublicvoidprocessOrder(Orderorder){// 整个方法是一个事务同生共死orderMapper.insert(order);inventoryService.deduct(order);paymentService.pay(order);}优点简洁事务和业务代码彻底分离。缺点事务范围固定整个方法无法在方法内灵活调整。4.2 编程式事务TransactionTemplatepublicvoidprocessOrder(Orderorder){// 第一步创建订单必须成功transactionTemplate.execute(status-{orderMapper.insert(order);returnnull;});// 第二步扣库存独立事务失败不影响订单创建try{transactionTemplate.execute(status-{inventoryService.deduct(order);returnnull;});}catch(Exceptione){log.error(扣库存失败但订单已创建,e);}// 第三步发送通知异步不需要事务notificationService.send(order);}优点事务边界灵活可在方法内精细控制。缺点事务代码和业务代码混在一起不如Transactional简洁。4.3 什么时候用编程式事务方法内需要多个不同传播行为的事务事务提交或回滚后需要继续执行其他操作而不是整个方法终止涉及异步、消息队列等复杂场景需要手动控制事务边界绝大多数场景用Transactional就够了。当它不够灵活时需要知道有编程式事务这个更灵活的工具。五、Spring事务和数据库事务的关系疑问Spring事务和数据库事务是什么关系Spring在这里加了什么回答Spring事务不是凭空产生的——它底层依赖数据库的事务能力。Spring做的不是在数据库之上再造一套事务机制而是统一管理何时开启、何时提交、何时回滚这套协调逻辑。Transactional → Spring层面的声明式事务 ↓ Spring的事务管理器PlatformTransactionManager ↓ 数据库连接Connection.setAutoCommit(false) ↓ 数据库事务InnoDB的ACID能力 Spring加了三层价值 1. 统一抽象不同数据库的事务API不同Spring屏蔽了差异 2. 传播行为控制事务在多个方法调用间的传递方式 3. 声明式注解Transactional让事务和业务代码彻底分离Spring事务本质是对数据库事务的封装和增强。底层还是用的数据库原生事务但Spring提供了传播行为、声明式注解、统一抽象等额外的管理能力。没有Spring你仍然可以通过Connection.setAutoCommit(false/true)手动控制事务——Spring让这个过程自动化、声明化。六、面试中这样回答面试官“Transactional是怎么实现的”回答框架“和AOP的原理一样——Spring为标注了Transactional的Bean生成代理对象。在代理对象中TransactionInterceptor拦截方法调用在方法前后通过事务管理器开启和提交/回滚事务。业务方法内部不需要写任何事务代码事务逻辑通过AOP从业务代码中剥离。这也解释了为什么自调用会失效——this调用绕过了代理对象事务拦截器无法生效。”面试官“Transactional在什么情况下会失效至少说三种。”回答“第一自调用——同一个类中方法互调this调用绕过了AOP代理。第二非public方法——CGLIB无法重写私有和受保护方法。第三异常被try-catch内部吞掉——事务拦截器完全感知不到异常直接提交。第四异常类型不匹配——Transactional默认只对RuntimeException和Error回滚CheckedException需要显式指定rollbackFor。第五数据库引擎不支持事务——比如MyISAMSpring事务管理无法在缺乏事务能力的引擎上生效。”总结Transactional通过TransactionInterceptorAOP代理实现——事务管理逻辑在代理对象中包裹业务方法业务代码与事务逻辑彻底分离传播行为定义事务在方法调用间的传递规则——REQUIRED同生共死REQUIRES_NEW独立提交。日志记录等辅助操作需要REQUIRES_NEW隔离失败影响自调用是最容易踩的坑——this调用绕过代理事务拦截器完全不知道你调用了它标注的方法。解决方式抽到另一个Service中或通过AopContext.currentProxy()获取代理对象异常被吞掉导致事务不回滚——Spring只在抛出异常时感知到失败。try-catch内部消化异常后拦截器看到方法正常返回直接提交事务声明式事务适合大多数场景需要方法内精细控制事务边界时用编程式事务。两者都是对数据库事务的封装和增强底层依赖数据库的事务能力完整的事务失效清单自调用绕过代理、非public方法、异常被吞掉、异常类型不匹配、MyISAM引擎——每次排查事务失效时按这个清单逐个排查下一篇预告Spring原理六——Spring用到了哪些设计模式从单例、工厂到代理、模板方法拆解Spring框架源码中的经典设计模式应用串联前五篇的IoC、AOP、MVC、自动配置、事务管理知识。

相关文章:

第五篇:Spring事务管理——@Transactional的底层实现与失效场景

前言 在前面的文章中,我们拆解了Spring AOP的底层原理——动态代理和切面编程。现在,我们来看AOP最经典的应用:事务管理。 你每天用着Transactional,往Service方法上一加,事务就自动开启了。但面试中,事务是…...

AI代理协作平台agtx:用终端看板管理多AI编程工作流

1. 项目概述:一个能管理其他AI编程代理的终端看板如果你和我一样,每天要在Claude、Cursor、Codex这些AI编程工具之间来回切换,同时处理多个功能需求,那你肯定也经历过这种混乱:一个终端窗口里,Claude正在写…...

SQL与数据库开发(四):CASE WHEN 与“行转列/列转行”花式玩法

在企业级应用的开发中,后端程序员和报表工程师往往面临着一种天然的矛盾:“数据库的存储格式”与“前端的展示格式”是完全不匹配的。 关系型数据库最喜欢“瘦长”的表(不断往下插入新行),而业务方和老板最喜欢看的是…...

Linux系统编程-makefile文件与make命令的使用

目录 一.makefile文件 1.1什么是makefile 1.2 makefile的一、二、三 1.2.1 一个规则 (1) 两个基本原则: (2) 使用 ALL 来指定makefile的终极目标: 1.2.2 两个函数 (1) src $(wildcard *.c) (2) obj $(patsubst %.c, %.o, $(src)) 1.2.3 三个…...

AI Agent集成Kalshi预测市场交易技能:自动化交易与风险管理实战

1. 项目概述:一个为AI Agent设计的Kalshi预测市场交易技能如果你对量化交易、自动化脚本或者新兴的AI Agent生态感兴趣,并且听说过“预测市场”这个概念,那么今天聊的这个项目可能会让你眼前一亮。lacymorrow/openclaw-kalshi-trading-skill本…...

AI伦理编程实战:从公平性算法到可解释性模型的工程实践

1. 项目概述:当代码开始思考,我们该教它什么? “AI伦理编程”这个词,听起来像是一个技术乌托邦,一个我们只要遵循几条规则就能让机器变得善良的简单任务。但当你真正坐下来,试图将“公平”、“透明”、“无…...

机器学习在非洲公共卫生疾病预测中的实战应用与技术解析

1. 项目概述:当AI遇见非洲公共卫生在非洲大陆,公共卫生系统长期面临着资源不均、基础设施薄弱和疾病负担沉重的多重挑战。传统的疾病监测依赖于被动报告和人工数据分析,往往存在滞后性,当疫情警报拉响时,病毒可能已经悄…...

机器学习在非洲传染病预测与监测中的实战应用

1. 项目概述:当AI遇见非洲传染病防控在公共卫生领域,时间就是生命,资源就是防线。对于非洲大陆而言,这句话的分量尤为沉重。这里常年承受着全球最沉重的传染病负担,从水源性传播的霍乱、致命性极高的埃博拉&#xff0c…...

AI赋能风景园林设计:技术原理、实践案例与未来挑战

1. 项目概述:当AI遇见园林最近几年,我身边不少做景观设计的朋友,从最初的“AI能画图?试试看”,到现在的“这个参数化模型帮我省了一周工作量”,态度转变非常明显。这让我意识到,人工智能在风景园…...

AI赋能区域创新评估:融合记分板与政策文本分析的协同框架与实践

1. 项目概述与核心价值 最近在梳理区域创新政策与人工智能应用交叉领域的工作时,我深度实践了一个项目,核心是探讨如何将欧盟的“区域创新记分板”这套成熟的评估体系,与新兴的AI政策分析工具进行深度融合与协同应用。这听起来可能有些学术化…...

ARM Trace单元架构与TRCVICTLR寄存器详解

1. ARM Trace单元架构概述在嵌入式系统开发领域,调试能力往往决定了问题定位的效率和质量。ARM架构提供的Trace单元(Embedded Trace Macrocell, ETM)作为处理器指令执行流追踪的核心组件,已经成为现代SoC调试基础设施的重要组成部…...

使用 Python 快速接入 Taotoken 并调用多模型 API 的完整指南

🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 使用 Python 快速接入 Taotoken 并调用多模型 API 的完整指南 对于希望快速集成大模型能力的 Python 开发者而言,逐一对…...

时序逻辑与值函数分解在强化学习中的应用

1. 时序逻辑与值函数分解的核心原理 时序逻辑(Temporal Logic, TL)作为形式化方法的重要分支,其本质是通过数学语言描述系统在时间维度上的行为约束。在控制理论与强化学习领域,TL的价值在于将复杂的任务需求转化为可计算的优化目…...

Arm架构DCU寄存器解析与安全调试实践

1. Arm生命周期管理器DCU寄存器深度解析 在Arm架构的嵌入式系统开发中,生命周期管理器(Lifecycle Manager, LCM)扮演着关键角色,而其中的调试控制单元(Debug Control Unit, DCU)寄存器组则是开发人员必须掌…...

ARM架构CNTP_CVAL寄存器详解与定时器编程实践

1. ARM架构中的CNTP_CVAL寄存器解析 在ARMv8/v9架构中,定时器系统是处理器关键的时间管理组件,而CNTP_CVAL(Counter-timer Physical Timer CompareValue Register)作为EL1物理定时器的比较值寄存器,在实时任务调度、中…...

AI 基本面量化:从理论到可部署 MVP-1.学习目标与工具链

AI 基本面量化实战:从理论到可部署 MVP 的完整学习路径1. 核心目标与 MVP 定义1.1 学习目标定位1.1.1 掌握 AI 技术与基本面分析深度融合的方法论体系AI 基本面量化的本质并非用复杂模型替代经典金融理论,而是以经济学逻辑为锚、以数据驱动为翼&#xff…...

物理 AI 为什么离不开边缘计算?

过去两年,AI 给人的印象基本是一回事——一个对话框,一个输入框。你打字它打字,你上传它分析,AI 安静地待在屏幕里,处理着一切关于文字、图像、代码的事情。行业的注意力也都跟着堆在那一头。云厂商抢算力,…...

3406硬核量化总结:黄大年茶思屋34期5题全解 重塑华为全球全栈技术霸权战略

华夏之光永存・硬核总结:黄大年茶思屋5题全解对华为战略的决定性价值 一、华为核心战略:全栈自主可控,构建端边云网芯一体化技术霸权 华为的核心战略是根技术全自研、全链路闭环、全场景覆盖,以芯片为底座、网络为联接、操作系统为中枢、AI为引擎、云为载体、行业应用为出…...

AI编程效率革命:Cursor Rules配置实战与团队协作指南

1. 项目概述:从“Cursor Rules”看现代开发者的效率革命最近在GitHub上看到一个名为usrrname/cursorrules的项目,这个标题乍一看有点意思,它直接点明了两个核心要素:cursor和rules。对于深度使用Cursor这款AI代码编辑器的开发者来…...

如何用python函数制作一个计算工具

大家好,这里是junlang的python文章 今天教大家如何用python函数做一个计算器,希望大家好好学习哦 如何制作 首先我们先定义4个函数,其中除法计算代码请看下面: def add (a,b,c):return (a b - c) def sub (x,y):return(x - y) def mulpl…...

星露谷物语模组加载器SMAPI:免费开源的游戏增强终极指南

星露谷物语模组加载器SMAPI:免费开源的游戏增强终极指南 【免费下载链接】SMAPI The modding API for Stardew Valley. 项目地址: https://gitcode.com/gh_mirrors/smap/SMAPI 星露谷物语模组加载器SMAPI是《星露谷物语》的官方模组API,为这款经典…...

DSP架构设计与低功耗优化关键技术解析

1. DSP架构基础与性能挑战数字信号处理器(DSP)与传统微控制器在架构设计上存在本质差异。微控制器主要面向控制任务——处理输入数据、做出决策并调整输出设备状态,而DSP的核心使命是维持连续数据流的高效处理。这种差异直接体现在硬件架构的…...

AI API智能调度中继服务:多账号管理与高可用架构实践

1. 项目概述:一个高性能的AI API智能调度中转站如果你手头有多个Claude、Gemini或者OpenAI的账号,并且经常在不同的开发工具(比如Claude Code CLI、各种SDK)之间切换使用,那你肯定体会过那种管理上的繁琐。每次调用都得…...

量子度量学习的黑盒验证协议设计与实现

1. 量子度量学习与黑盒验证概述量子度量学习(Quantum Metric Learning)是量子机器学习领域的一个重要分支,其核心目标是通过优化量子特征映射,将经典数据转换为量子希尔伯特空间中的态,使得不同类别的数据在量子态空间…...

从零构建智能文档工厂:自动化生成API文档与多格式发布

1. 项目概述:从“文档生成”到“智能文档工厂”在软件开发和团队协作的日常里,文档工作常常被戏称为“脏活累活”。它不像写代码那样有即时的反馈和成就感,但又不可或缺。无论是API接口文档、项目说明、还是内部流程手册,一份清晰…...

微信聊天记录永久保存与深度分析:你的数字记忆守护者

微信聊天记录永久保存与深度分析:你的数字记忆守护者 【免费下载链接】WeChatMsg 提取微信聊天记录,将其导出成HTML、Word、CSV文档永久保存,对聊天记录进行分析生成年度聊天报告 项目地址: https://gitcode.com/GitHub_Trending/we/WeChat…...

UCSC基因组浏览器可视化配置实战:从参数调优到多组学数据呈现

1. UCSC基因组浏览器入门:为什么选择它? 第一次接触UCSC基因组浏览器是在分析RNA-seq数据时,当时需要直观展示基因表达差异。这个由加州大学圣克鲁兹分校维护的工具,已经成为生物信息学领域的标准配置。它最吸引我的特点是零代码…...

在 Python 中使用 comtypes 时,大小写通常必须保持精确

wb excel.Workbooks.Open(file_path)print(f"文件已打开: {file_path}")后面的方法,大小写可以写错吗?这是一个非常经典的问题,答案是:在 Python 中使用 comtypes 时,大小写通常必须保持精确,不…...

SingleFile:一站式网页归档解决方案,让网页永久保存不再是难题

SingleFile:一站式网页归档解决方案,让网页永久保存不再是难题 【免费下载链接】SingleFile Web Extension for saving a faithful copy of a complete web page in a single HTML file 项目地址: https://gitcode.com/gh_mirrors/si/SingleFile …...

Ironclaw:基于Rust的现代化命令行工具集,重塑开发效率

1. 项目概述:一个面向开发者的现代化命令行工具集在当今的软件开发工作流中,命令行界面(CLI)依然是开发者与系统、服务交互的核心桥梁。无论是进行本地开发、自动化部署、系统运维还是数据处理,一个高效、可靠、符合直…...