1. 入口:开启事务功能
要让 @Transactional 生效,首先需要通过注解开启事务功能。
- XML 时代:
<tx:annotation-driven /> - Spring Boot 时代:
@EnableTransactionManagement(通常 Spring Boot 自动配置已经帮我们加上了)
当 Spring 容器启动时,这个注解会触发一个关键的 **BeanPostProcessor**(Bean 后置处理器),即 InfrastructureAdvisorAutoProxyCreator。
2. 核心:代理机制
Spring 不会修改你的原始类代码,而是为你生成一个 代理对象。
- 扫描:容器启动时,
BeanPostProcessor会扫描所有的 Bean,判断它们是否被@Transactional标记(或者类中是否有方法标记了该注解)。 - 代理决策:
-
- 如果发现某个 Bean 需要事务支持,Spring 就不会直接返回这个 Bean 的实例。
- 它会根据该类是否实现了接口,决定使用 JDK 动态代理(基于接口)还是 CGLIB(基于子类)生成一个代理对象。
- 注入代理:当你从
ApplicationContext中获取这个 Bean,或者在其他 Bean 中通过@Autowired注入时,实际上注入的是 代理对象,而不是原始对象。
3. 执行:拦截器链路
当你在业务代码中调用被 @Transactional 注解的方法时,流程如下:
调用 **methodA()**
↓
代理对象(Proxy)
↓
**TransactionInterceptor**(事务拦截器)
↓
**PlatformTransactionManager**(事务管理器)
关键步骤:TransactionInterceptor
这个拦截器是事务生效的“指挥中心”。它的工作流程大致是:
- 获取事务定义:读取
@Transactional注解上的属性(传播行为、隔离级别、超时时间、只读标志等)。 - 开启事务:调用
PlatformTransactionManager.getTransaction()。 -
- 根据传播行为(
PROPAGATION_REQUIRED等),决定是新建一个数据库连接,还是挂起当前事务,复用已有连接。 - 将
auto-commit设置为 false。 - 将事务状态(
TransactionStatus)绑定到当前线程的ThreadLocal中。
- 根据传播行为(
- 执行目标方法:调用原始类的实际业务逻辑。
- 处理结果:
-
- 如果方法正常返回:拦截器调用
transactionManager.commit()。Spring 会执行 SQL 提交,并清理线程绑定的事务资源。 - 如果方法抛出异常:
-
- 检查异常类型。如果是 unchecked 异常(
RuntimeException及其子类)或 Error,默认回滚。 - 如果是 checked 异常(如
IOException),默认不回滚(除非你配置了rollbackFor)。 - 执行
transactionManager.rollback()。
- 检查异常类型。如果是 unchecked 异常(
- 如果方法正常返回:拦截器调用
4. 关键组件:事务管理器
你可能会问,Spring 是如何操作数据库连接进行 commit 和 rollback 的?这依赖于 **PlatformTransactionManager**(平台事务管理器)。
不同的持久层框架需要不同的实现:
- JPA(Hibernate):
JpaTransactionManager - JDBC/MyBatis:
DataSourceTransactionManager
这些管理器负责从数据源获取连接,并通过 资源同步 将连接绑定到当前线程。
连接是如何自动传递的?
你可能注意到,在 DAO 层(使用 MyBatis 或 JdbcTemplate)并没有手动把 Connection 传入,但 SQL 却自动纳入了事务。这是因为:
- 当
TransactionInterceptor开启事务时,DataSourceTransactionManager从数据源获取一个连接。 - 它将这个连接通过
TransactionSynchronizationManager.bindResource绑定到当前线程的 Map 中。 - 当你执行 SQL 时(如
JdbcTemplate),它会调用DataSourceUtils.getConnection,该方法会优先从ThreadLocal中取出这个已经开启了事务的连接。 - 所以,整个过程使用的都是同一个连接,确保了事务的原子性。
5. 常见失效原因(易错点)
了解了原理,就不难理解为什么 @Transactional 有时会“不生效”:
- 自调用:同一个类中,A 方法(无事务)调用 B 方法(有
@Transactional)。 -
- 原因:因为是直接通过
this调用,绕过了代理对象,拦截器没有机会执行。
- 原因:因为是直接通过
- 访问权限:
@Transactional加在private或final方法上。 -
- 原因:CGLIB 代理无法重写
private和final方法。
- 原因:CGLIB 代理无法重写
- 异常类型:代码抛出了 Exception,但默认只对
RuntimeException回滚。 -
- 解决:指定
@Transactional(rollbackFor = Exception.class)。
- 解决:指定
- 异常被吞:在
try-catch中捕获了异常但没有抛出,拦截器认为方法正常执行结束,于是执行了 commit。
总结
Spring 的事务自动生效机制可以概括为:
**通过 AOP 机制,在 Bean 初始化阶段生成代理对象,在方法调用时通过拦截器(**
**TransactionInterceptor****)在目标方法前后插入开启事务(****begin****)、提交事务(****commit****)或回滚事务(****rollback****)的逻辑,并通过事务管理器(****PlatformTransactionManager****)和线程绑定资源(****ThreadLocal**)来确保同一线程下的 DAO 操作共享同一个数据库连接。