Spring `@Transactional` 注解生效原理

3 阅读4分钟

1. 入口:开启事务功能

要让 @Transactional 生效,首先需要通过注解开启事务功能。

  • XML 时代<tx:annotation-driven />
  • Spring Boot 时代@EnableTransactionManagement(通常 Spring Boot 自动配置已经帮我们加上了)

当 Spring 容器启动时,这个注解会触发一个关键的 **BeanPostProcessor**(Bean 后置处理器),即 InfrastructureAdvisorAutoProxyCreator

2. 核心:代理机制

Spring 不会修改你的原始类代码,而是为你生成一个 代理对象

  1. 扫描:容器启动时,BeanPostProcessor 会扫描所有的 Bean,判断它们是否被 @Transactional 标记(或者类中是否有方法标记了该注解)。
  2. 代理决策
    • 如果发现某个 Bean 需要事务支持,Spring 就不会直接返回这个 Bean 的实例。
    • 它会根据该类是否实现了接口,决定使用 JDK 动态代理(基于接口)还是 CGLIB(基于子类)生成一个代理对象。
  3. 注入代理:当你从 ApplicationContext 中获取这个 Bean,或者在其他 Bean 中通过 @Autowired 注入时,实际上注入的是 代理对象,而不是原始对象。

3. 执行:拦截器链路

当你在业务代码中调用被 @Transactional 注解的方法时,流程如下:

调用 **methodA()**

代理对象(Proxy)

**TransactionInterceptor**(事务拦截器)

**PlatformTransactionManager**(事务管理器)

关键步骤:TransactionInterceptor

这个拦截器是事务生效的“指挥中心”。它的工作流程大致是:

  1. 获取事务定义:读取 @Transactional 注解上的属性(传播行为、隔离级别、超时时间、只读标志等)。
  2. 开启事务:调用 PlatformTransactionManager.getTransaction()
    • 根据传播行为(PROPAGATION_REQUIRED 等),决定是新建一个数据库连接,还是挂起当前事务,复用已有连接。
    • 将 auto-commit 设置为 false。
    • 将事务状态(TransactionStatus)绑定到当前线程的 ThreadLocal 中。
  3. 执行目标方法:调用原始类的实际业务逻辑。
  4. 处理结果
    • 如果方法正常返回:拦截器调用 transactionManager.commit()。Spring 会执行 SQL 提交,并清理线程绑定的事务资源。
    • 如果方法抛出异常
      • 检查异常类型。如果是 unchecked 异常RuntimeException 及其子类)或 Error,默认回滚。
      • 如果是 checked 异常(如 IOException),默认不回滚(除非你配置了 rollbackFor)。
      • 执行 transactionManager.rollback()

4. 关键组件:事务管理器

你可能会问,Spring 是如何操作数据库连接进行 commit 和 rollback 的?这依赖于 **PlatformTransactionManager**(平台事务管理器)。

不同的持久层框架需要不同的实现:

  • JPA(Hibernate)JpaTransactionManager
  • JDBC/MyBatisDataSourceTransactionManager

这些管理器负责从数据源获取连接,并通过 资源同步 将连接绑定到当前线程。

连接是如何自动传递的?

你可能注意到,在 DAO 层(使用 MyBatis 或 JdbcTemplate)并没有手动把 Connection 传入,但 SQL 却自动纳入了事务。这是因为:

  1. 当 TransactionInterceptor 开启事务时,DataSourceTransactionManager 从数据源获取一个连接。
  2. 它将这个连接通过 TransactionSynchronizationManager.bindResource 绑定到当前线程的 Map 中。
  3. 当你执行 SQL 时(如 JdbcTemplate),它会调用 DataSourceUtils.getConnection,该方法会优先从 ThreadLocal 中取出这个已经开启了事务的连接。
  4. 所以,整个过程使用的都是同一个连接,确保了事务的原子性。

5. 常见失效原因(易错点)

了解了原理,就不难理解为什么 @Transactional 有时会“不生效”:

  1. 自调用:同一个类中,A 方法(无事务)调用 B 方法(有 @Transactional)。
    • 原因:因为是直接通过 this 调用,绕过了代理对象,拦截器没有机会执行。
  2. 访问权限@Transactional 加在 private 或 final 方法上。
    • 原因:CGLIB 代理无法重写 private 和 final 方法。
  3. 异常类型:代码抛出了 Exception,但默认只对 RuntimeException 回滚。
    • 解决:指定 @Transactional(rollbackFor = Exception.class)
  4. 异常被吞:在 try-catch 中捕获了异常但没有抛出,拦截器认为方法正常执行结束,于是执行了 commit。

总结

Spring 的事务自动生效机制可以概括为:

**通过 AOP 机制,在 Bean 初始化阶段生成代理对象,在方法调用时通过拦截器(****TransactionInterceptor****)在目标方法前后插入开启事务(****begin****)、提交事务(****commit****)或回滚事务(****rollback****)的逻辑,并通过事务管理器(****PlatformTransactionManager****)和线程绑定资源(****ThreadLocal**)来确保同一线程下的 DAO 操作共享同一个数据库连接。