Spring 事务终极笔记 (V5.5 - 最终定稿版)[From Manus]
前言:思维路线 (Thinking Roadmap)
思路
本笔记旨在提供一份关于 Spring 事务的系统性、深度总结。为了帮助读者更好地理解和吸收,我们遵循以下一条清晰的逻辑脉络来组织内容,从“为什么”出发,逐步深入到“是什么”、“如何实现”、“如何正确使用”,并最终将视野扩展到更广阔的分布式领域。
-
** 第一章:Why? - 为什么需要 Spring 事务?**
- 我们将从历史背景切入,回顾在 Spring 出现之前,开发者使用原生 JDBC 和 JTA 管理事务时所面临的繁琐与痛点。通过强烈的“前后对比”,你将深刻理解 Spring 事务管理的设计初衷和其带来的巨大价值。
-
** 第二章:What? - 事务的通用核心理论是什么?**
- 在深入 Spring 的世界之前,本章将为你铺垫所有事务技术都必须遵循的通用理论基础,包括经典的 ACID 原则、事务实现的数据库根基,以及最重要的——明确本地事务与分布式事务的边界。
-
** 第三章:How? - Spring 是如何实现事务的?**
- 本章将深入 Spring 框架的内部,揭示其实现声明式事务的魔法。你将了解到 AOP 动态代理是如何工作的,以及
PlatformTransactionManager
等三大核心组件是如何协同支撑起整个事务体系的。
- 本章将深入 Spring 框架的内部,揭示其实现声明式事务的魔法。你将了解到 AOP 动态代理是如何工作的,以及
-
** 第四章:Practice - 如何正确地使用 Spring 事务?**
- 理论最终要服务于实践。本章将聚焦于日常开发,总结
@Transactional
注解在真实场景下最容易“失效”的陷阱,并提供一系列经过检验的最佳实践,帮助你写出更健壮的代码。
- 理论最终要服务于实践。本章将聚焦于日常开发,总结
-
** 第五章:Beyond - 如何从本地事务走向分布式事务?**
- 在微服务时代,分布式事务是绕不开的话题。本章将作为从本地事务到分布式架构的桥梁,系统性地介绍 Spring 生态是如何与业界主流的分布式事务解决方案进行集成的,并深入剖析其工作原理及 Spring 在其中的角色。
我们希望通过这条精心设计的学习路径,让你对 Spring 事务的理解不再是零散的知识点,而是一个融会贯通、从宏观到微观的完整知识体系。
第一章:Spring 事务的诞生:从历史背景说起
本章将从历史背景出发,探讨在 Spring 出现之前,Java 开发者是如何进行事务管理的,以及他们面临的痛点。理解这些,才能真正体会到 Spring 事务管理的设计之妙。
- 参考资料:
1.1 黎明之前:JDBC 事务的繁琐
- 实现方式: 开发者需要手动控制
java.sql.Connection
对象,并编写大量模板代码。// JDBC 事务代码示例 Connection conn = null; try { conn = dataSource.getConnection(); conn.setAutoCommit(false); // 开启事务 // --- 核心业务逻辑 --- conn.commit(); // 提交事务 } catch (SQLException e) { if (conn != null) { conn.rollback(); } // 回滚事务 throw new RuntimeException(e); } finally { if (conn != null) { conn.close(); } // 关闭连接 }
- 核心痛点: 模板化的冗余代码、与业务逻辑强耦合、资源管理复杂且易错。
⚖️ 1.2 另一种选择:JTA 事务的重量级
- 实现方式: JTA 将事务管理从具体资源中抽象出来,由独立的“事务管理器”协调。
// JTA 事务的伪代码/概念示例 UserTransaction utx = (UserTransaction) new InitialContext().lookup("java:comp/UserTransaction"); try { utx.begin(); // 开启全局事务 // --- 核心业务逻辑 (操作多个数据源) --- utx.commit(); // 提交全局事务 } catch (Exception e) { if (utx != null) { utx.rollback(); } // 回滚全局事务 throw new RuntimeException(e); }
- 核心痛点: API 复杂、强依赖重量级的应用服务器。
✨ 1.3 Spring 的登场:一个注解的优雅
面对上述的“繁琐”与“重量级”,Spring 提出了一个革命性的解决方案。
- 实现方式: 开发者只需在业务方法上添加一个简单的
@Transactional
注解。// Spring 声明式事务代码示例 @Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Transactional public void createUser(User user) { // --- 纯粹的核心业务逻辑 --- userDao.insert(user); } }
- 优势对比: 简洁与解耦、轻量级与可移植、功能强大。
第二章:事务的通用理论与边界
在深入 Spring 的实现之前,我们必须先掌握事务的通用理论。这是理解一切事务管理技术的基础。
2.1 事务的 ACID 原则
- 原子性 (Atomicity): 所有操作要么一起成功,要么一起失败。
- 一致性 (Consistency): 数据在事务前后都处于合法的业务状态。
- 隔离性 (Isolation): 并发事务之间互不影响。
- 持久性 (Durability): 事务一旦提交,其结果永久保存。
- 参考资料:
️ 2.2 事务的实现根基:数据库
数据库是事务的基石。如果底层数据库或其存储引擎(如 MySQL 的 MyISAM)不支持事务,应用程序层面无法实现真正的事务性操作。
- 参考资料:
2.3 事务的边界:本地事务 vs. 分布式事务
- 本地事务 (Local Transaction): 由单个数据资源管理器控制的事务。Spring 的
@Transactional
本质上是对本地事务的管理。 - 分布式事务 (Distributed Transaction): 跨越多个独立数据资源或服务的事务。Spring 生态可与 Seata 等第三方框架集成以支持分布式事务。
- 参考资料:
第三章:Spring 事务的实现原理与核心组件
本章将深入 Spring 内部,探讨其事务管理的实现原理和三大核心组件。
3.1 实现原理:基于 AOP 的动态代理
Spring 实现声明式事务的核心技术是 AOP (面向切面编程)。
- 基础: 整个机制依赖于 Spring 的 IoC 容器来管理 Bean,以及 AOP 来动态增强 Bean。
- 核心流程:
- 扫描与识别: Spring 容器启动时,会扫描并识别出所有被
@Transactional
注解标记的方法。 - 创建代理对象: 当一个 Bean 中含有
@Transactional
方法时,Spring AOP 会在 Bean 的初始化阶段,为这个 Bean 创建一个代理对象 (Proxy)。这个代理对象封装了事务处理的逻辑。 - 注入代理对象: Spring 最终放入 IoC 容器中供其他组件使用的,是这个代理对象,而不是原始的 Bean 对象。
- 调用与拦截: 当外部代码调用该 Bean 的事务方法时,实际上是调用了代理对象的方法。代理对象会在执行真实业务逻辑之前开启事务,在之后根据执行结果提交或回滚事务,从而实现了对业务代码无侵入的事务管理。
- 扫描与识别: Spring 容器启动时,会扫描并识别出所有被
- 参考资料:
️ 3.2 核心组件:三大基石接口
-
PlatformTransactionManager
(事务管理器):- 职责: Spring 事务管理的核心接口,负责适配和封装底层事务技术,提供
getTransaction()
,commit()
,rollback()
等标准操作。 - 参考文档:
- 职责: Spring 事务管理的核心接口,负责适配和封装底层事务技术,提供
-
TransactionDefinition
(事务定义):- 职责: 描述事务的属性。
@Transactional
注解的所有参数最终都会被解析成一个TransactionDefinition
对象。 - 核心属性 -
propagation
(传播行为):REQUIRED
(默认): 如果当前存在事务,则加入;否则,创建一个新事务。REQUIRES_NEW
: 总是创建一个全新的、独立的事务。NESTED
: 创建一个嵌套事务(保存点),可独立回滚,但提交依赖外部事务。SUPPORTS
: 支持当前事务,但如果不存在,就以非事务方式执行。NOT_SUPPORTED
: 总是以非事务方式执行,如果存在事务,则挂起。MANDATORY
: 强制要求当前必须存在事务,否则抛出异常。NEVER
: 强制要求当前不能存在事务,否则抛出异常。
- 核心属性 -
isolation
(隔离级别):DEFAULT
: 使用数据库的默认隔离级别。READ_UNCOMMITTED
: 读未提交。READ_COMMITTED
: 读已提交。REPEATABLE_READ
: 可重复读。SERIALIZABLE
: 串行化。
- 参考文档:
- 职责: 描述事务的属性。
-
TransactionStatus
(事务状态):- 职责: 代表一个正在运行中的特定事务。
- 核心状态与操作:
isNewTransaction()
: 判断当前是否为新事务。isRollbackOnly()
: 判断当前事务是否已被标记为只能回滚。setRollbackOnly()
: 以编程方式将当前事务标记为只能回滚。
- 参考文档:
第四章:实践中的陷阱与最佳实践
本章总结了在使用 @Transactional
时最常见的“坑”以及相应的最佳实践。
⚠️ 4.1 @Transactional
失效的常见场景
- 应用在非
public
方法上。 - 方法内部通过
this
调用,绕过了 AOP 代理。 rollbackFor
属性设置错误, 未覆盖抛出的异常类型(Spring 默认只回滚RuntimeException
和Error
)。- 数据库引擎不支持事务。
- 方法内部
catch
了异常但未重新抛出。 - 参考资料:
✅ 4.2 最佳实践
- 将注解应用于 Service 层的
public
方法上。 - 明确指定事务属性,而非依赖默认值。
- 保持事务方法的职责单一。
- 警惕内部调用问题, 必要时将方法抽取到其他 Bean 中。
- 参考资料:
第五章:Spring 与分布式事务的集成方案
在微服务时代,分布式事务是绕不开的话题。本章将作为从本地事务到分布式架构的桥梁,系统性地介绍 Spring 生态是如何与业界主流的分布式事务解决方案进行集成的。
核心前提:Spring 的角色定位
在所有分布式事务方案中,Spring 的角色始终是本地事务的管理者和业务逻辑的承载者。分布式事务框架则扮演全局事务的协调者。二者的关系是分工协作:
- Spring (
@Transactional
): 负责管理单个微服务内部与数据库之间的本地事务,确保该服务内的数据操作符合 ACID。 - 分布式事务框架: 负责跨越多个微服务,协调它们各自的本地事务,以确保全局业务的最终一致性。
5.1 强一致性方案:XA/2PC (以 Atomikos 为例)
- 核心思想: 将所有参与者绑定到一个“要么全成功,要么全失败”的原子操作中,追求数据的强一致性。
- 组件角色:
- 应用程序 (AP): 业务代码的载体,由 Spring 管理。
- 事务管理器 (TM - 如 Atomikos): 全局事务的协调者,负责发起两阶段提交协议。
- 资源管理器 (RM - 如数据库驱动): 负责管理本地资源,并实现 XA 规范接口。
- 与 Spring 的集成:
- 依赖引入: 在 Spring Boot 项目中加入
spring-boot-starter-jta-atomikos
。 - 配置替换: Spring Boot 的自动配置机制会检测到 Atomikos 的存在,用
JtaTransactionManager
替换掉默认的DataSourceTransactionManager
。 - 数据源配置: 开发者需要将所有数据源配置为
AtomikosDataSourceBean
,将其注册到 Atomikos (TM) 中。
- 依赖引入: 在 Spring Boot 项目中加入
- 详细流程:
- 开启全局事务: 开发者在业务入口方法上标注
@Transactional
。由于PlatformTransactionManager
的实现已被替换为JtaTransactionManager
,此注解会通知 Atomikos (TM) 开启一个全局事务。 - 执行业务: 业务代码通过
JdbcTemplate
或 JPA 操作多个数据源。 - 第一阶段 (Prepare): 当业务方法即将结束时,Atomikos (TM) 向所有参与该全局事务的数据库 (RM) 发送
prepare
指令。各个数据库执行本地事务,写redo
和undo
日志,然后锁定资源并报告“准备就
绪”。 - 第二阶段 (Commit/Rollback):
- 若全部成功: Atomikos (TM) 向所有数据库发送
commit
指令,各数据库提交本地事务。 - 若有任一失败: Atomikos (TM) 向所有数据库发送
rollback
指令,各数据库利用undo
日志回滚本地事务。
- 若全部成功: Atomikos (TM) 向所有数据库发送
- 开启全局事务: 开发者在业务入口方法上标注
- Spring 在此的角色: Spring 的
@Transactional
成为了触发全局事务的入口。它将事务管理的职责完全委托给了外部的 JTA 事务管理器 (Atomikos),自己不再直接控制数据库连接的commit
和rollback
。 - 参考资料:
5.2 最终一致性方案:TCC (以 Seata TCC 模式为例)
- 核心思想: 补偿型事务。通过“预留资源”和“确认/取消资源”两个阶段,将重量级的数据库锁转化为轻量级的业务状态锁定。
- 组件角色:
- 事务协调器 (TC - Seata Server): 记录全局事务和分支事务的状态,驱动第二阶段的
Confirm
或Cancel
。 - 事务管理器 (TM - Seata Client): 嵌入在 Spring 应用中,负责注册分支事务、上报状态。
- 资源管理器 (RM - 开发者业务代码): 开发者需要手动实现
Try
,Confirm
,Cancel
三个方法。
- 事务协调器 (TC - Seata Server): 记录全局事务和分支事务的状态,驱动第二阶段的
- 与 Spring 的集成:
- 依赖与配置: 在 Spring Boot 项目中引入 Seata 客户端依赖,并配置 Seata Server 地址。
- 注解驱动:
- 在全局事务的发起方,使用 Seata 的
@GlobalTransactional
注解。 - 在各个参与方的业务 Bean 中,实现 TCC 接口,并使用
@TwoPhaseBusinessAction
等注解标记Try
,Confirm
,Cancel
方法。这些 Bean 依然由 Spring 管理。
- 在全局事务的发起方,使用 Seata 的
- 详细流程:
- 开启全局事务: TM 向 TC 注册一个全局事务,拿到全局事务 ID (XID)。
- 第一阶段 (Try): 全局事务发起方调用参与方 A 的
Try
方法。TM 会拦截此调用,向 TC 注册一个分支事务,然后执行Try
逻辑(如冻结库存)。Try
方法执行成功后,TM 向 TC 报告分支事务状态为“已准备”。 - 第二阶段 (Confirm/Cancel):
- 若全部
Try
成功: TC 识别到全局事务状态为“可提交”,会异步地调用所有参与方之前注册的Confirm
方法,完成业务。 - 若有任一
Try
失败: TC 识别到全局事务状态为“需回滚”,会异步地调用所有已成功Try
的参与方的Cancel
方法,释放预留资源。
- 若全部
- Spring 在此的角色: Spring 依然是业务 Bean (RM) 的管理者。它负责创建和管理实现了 TCC 接口的 Service Bean。Seata 框架通过 AOP 拦截对这些 Bean 方法的调用,将其纳入自己的全局事务管理体系中。Spring 的本地事务 (
@Transactional
) 可以在Confirm
和Cancel
方法中使用,以保证这两个操作自身的原子性。 - 参考资料:
- Seata Official Docs: TCC Mode
- Seata Official Docs: Spring Boot Integration
- Microservices.io: Saga Pattern (TCC and Saga are often compared)
5.3 最终一致性方案:可靠消息最终一致性 (以 RocketMQ 为例)
- 核心思想: 将分布式事务解耦为一系列通过消息驱动的本地事务,利用消息队列的可靠性来保证最终一致性。
- 组件角色:
- 消息生产者 (Producer): 事务发起方,负责执行本地事务并发送消息。
- 消息队列 (MQ - 如 RocketMQ Server): 消息的中转站和存储中心,提供可靠消息机制。
- 消息消费者 (Consumer): 事务参与方,负责消费消息并执行本地事务。
- 与 Spring 的集成:
- 依赖与配置: 在 Spring Boot 项目中引入
rocketmq-spring-boot-starter
,并配置 NameServer 地址、生产者/消费者组等。 - 代码实现:
- 生产者: 在 Spring Service 中注入
RocketMQTemplate
。 - 消费者: 创建一个 Spring Service 并实现
RocketMQListener
接口,或使用@RocketMQMessageListener
注解标记一个方法。
- 生产者: 在 Spring Service 中注入
- 依赖与配置: 在 Spring Boot 项目中引入
- 详细流程 (以 RocketMQ 事务消息为例):
- 发送半消息 (Half Message): 生产者(上游服务)向 MQ Server 发送一条“半消息”。此消息对消费者不可见。
- 执行本地事务: MQ Server 返回“半消息发送成功”的 ACK 后,生产者开始执行自己的本地事务(例如,创建订单)。此操作由 Spring 的
@Transactional
管理。 - 提交/回滚半消息:
- 若本地事务成功: 生产者向 MQ Server 发送
commit
请求,MQ Server 将“半消息”标记为可投递的正常消息。 - 若本地事务失败: 生产者向 MQ Server 发送
rollback
请求,MQ Server 删除“半消息”。
- 若本地事务成功: 生产者向 MQ Server 发送
- 消息投递与消费: 消费者(下游服务)拉取到可投递的消息,并执行自己的本地事务(例如,扣减库存)。此本地事务同样由 Spring 的
@Transactional
管理。 - 状态回查: 如果生产者在第 3 步失联(如宕机),MQ Server 会定时回查生产者的本地事务状态(通过调用生产者预留的一个回查接口),以决定是
commit
还是rollback
半消息。
- Spring 在此的角色: Spring 在这个模式中扮演了至关重要的双重角色:
- 作为业务逻辑的承载者: 生产者和消费者的业务逻辑都运行在 Spring 管理的 Bean 中。
- 作为本地事务的守护者: 在生产者端,“执行本地事务”这一关键步骤的原子性由 Spring 的
@Transactional
保证。在消费者端,消费消息后的业务处理,其原子性也由 Spring 的@Transactional
保证。Spring 确保了分布式事务链条中每一个环节的内部数据一致性。
- 参考资料:
- RocketMQ Official Docs: Transaction Message
- Alibaba Cloud: Use Spring to send and subscribe to messages
- InfoQ: How to Ensure Transactional Messaging with Kafka (Explains the concept, applicable to MQ in general)