Spring 事务机制深度解析
本文档基于对 Spring Framework 源码的深入分析,详细阐述了 Spring 事务的管理机制、核心流程以及与 JDBC/JPA 的交互细节。
1. 核心架构组件
Spring 事务管理采用策略模式与模板方法模式相结合的设计,主要包含以下核心组件:
| 组件 | 职责 |
|---|---|
| PlatformTransactionManager | 事务管理器接口,定义了getTransaction,commit,rollback等核心操作。 |
| TransactionDefinition | 事务定义,包含传播行为、隔离级别、超时时间、只读标志等属性。 |
| TransactionStatus | 事务状态,记录当前事务是否为新事务、是否已完成、是否标记为回滚等。 |
| TransactionInterceptor | AOP 拦截器,负责在方法调用前后织入事务逻辑。 |
| TransactionSynchronizationManager | 使用ThreadLocal管理当前线程绑定的资源(如 Connection)和同步回调。 |
2. 事务生命周期与调用链路
2.1 完整调用流程
当一个带有@Transactional注解的方法被调用时,执行流程如下:
- AOP 拦截:
TransactionInterceptor.invoke()捕获方法调用。 - 获取事务属性: 解析方法或类上的
@Transactional注解,生成TransactionAttribute(txAttr)。 - 创建/加入事务: 调用
createTransactionIfNecessary()。- 若
txAttr == null,直接执行业务逻辑,不走事务流程。 - 若
txAttr != null,调用PlatformTransactionManager.getTransaction()。
- 若
- 开启事务 (doBegin):
- 如果需要新事务,调用
DataSourceTransactionManager.doBegin()。 - 获取数据库连接,设置
autocommit = false。 - 将连接绑定到
TransactionSynchronizationManager。
- 如果需要新事务,调用
- 执行业务逻辑: 调用目标方法(如 Service 中的业务代码)。
- 提交/回滚:
- 成功: 调用
commitTransactionAfterReturning()->doCommit()->con.commit()。 - 异常: 调用
completeTransactionAfterThrowing()->doRollback()->con.rollback()。
- 成功: 调用
- 清理资源: 解绑连接,恢复
autocommit状态,归还连接池。
2.2 doBegin() 的调用时机
doBegin()不会在以下情况调用:
- 方法没有
@Transactional注解 (txAttr == null)。 - 传播行为为
SUPPORTS且当前无事务。 - 传播行为为
NOT_SUPPORTED。 - 传播行为为
REQUIRED但当前已存在事务(此时会加入现有事务)。
doBegin()会在以下情况调用:
- 传播行为为
REQUIRED/REQUIRES_NEW/NESTED。 - 且当前线程没有活跃的事务(或者
REQUIRES_NEW强制挂起旧事务开启新的)。
3. 无事务注解时的行为分析
3.1 纯 JDBC (JdbcTemplate) 场景
如果 Service 方法和 Repository 均未添加@Transactional:
- 事务管理: Spring不介入事务管理,
doBegin()不会被调用。 - 连接获取: 每次数据库操作都会从连接池获取一个新连接。
- 提交机制: 依赖 JDBC 驱动的Auto-Commit机制。
Connection.getAutoCommit()默认为true。- 每条 SQL 执行完毕后,JDBC 驱动会自动向数据库发送
COMMIT。
- 后果: 每条 SQL 都是一个独立的原子操作,无法保证多条操作之间的原子性。
3.2 Spring Data JPA (Repository) 场景
如果 Service 方法未加@Transactional,但调用了userRepository.save():
- 隐式事务: Spring Data JPA 的默认实现类
SimpleJpaRepository的save()方法上标注了@Transactional。 - 调用位置:
doBegin()会在Repository 层被调用。 - 执行流程:
- Service 调用
repository.save()。 - AOP 拦截
SimpleJpaRepository.save()。 - 发现注解,触发
doBegin(),开启一个短事务。 - 执行
em.persist(),SQL 在事务提交前 Flush。 - 方法结束,触发
doCommit()。
- Service 调用
- 后果: 虽然单条保存有事务保障,但 Service 层多次调用 Repository 时,每次调用都是独立的事务。如果中间发生异常,之前已提交的 Repository 操作无法回滚。
4. 关键设计思想
4.1 声明式事务 (Declarative Transaction)
通过 AOP 将事务逻辑与业务逻辑解耦。开发者只需添加注解,无需手动编写begin/commit/rollback代码。
4.2 资源绑定 (Resource Binding)
利用ThreadLocal确保同一个事务内的所有数据库操作使用同一个 Connection。这是实现事务原子性的基础。
4.3 延迟提交 (Lazy Commit)
在事务开启后,SQL 执行并不会立即提交到数据库,而是等到业务方法成功执行完毕,由 Spring 统一调用con.commit()。
4.4 异常驱动回滚
默认情况下,只有遇到RuntimeException或Error时才会触发回滚。Checked Exception 默认不回滚,除非显式配置rollbackFor。
5. 常见误区澄清
| 误区 | 真相 |
|---|---|
| 没加注解,SQL 就不会执行 | SQL 会正常执行,并通过 Auto-Commit 立即持久化。 |
| Spring 会自动为所有方法加事务 | Spring 遵循“显式优于隐式”,必须显式添加注解或配置。 |
| Repository 层的 save 总是安全的 | 如果 Service 层没事务,Repository 的 save 只是单条 SQL 的原子,无法保证业务层面的原子性。 |
| txAttr 为 null 时也会调用 doBegin | 绝对不会。txAttr == null是事务拦截的第一道防线,直接跳过事务逻辑。 |
6. 总结
Spring 事务的核心在于AOP 拦截与Connection 资源的线程绑定。
- 有注解时: Spring 接管连接,关闭 Auto-Commit,统一控制提交与回滚。
- 无注解时: 回归 JDBC 默认行为,依赖 Auto-Commit,每条 SQL 独立提交。
在实际开发中,为了保证业务数据的 consistency(一致性),建议在Service 层的入口方法上添加@Transactional,以确保整个业务单元作为一个完整的事务执行。
