一、Spring事务机制的底层原理
1.@Transactional的本质
@Transactional的本质是AOP 代理 + 事务拦截器:调用方调用的是代理对象,事务的开启 / 提交 / 回滚全部由TransactionInterceptor织入完成。
原理总结:
@EnableTransactionManagement开启事务能力 → 容器为 Bean 生成 AOP 代理 → 方法调用进入TransactionInterceptor→ 用PlatformTransactionManager管理事务生命周期,数据库连接绑定在当前线程的 ThreadLocal,从而保证同一事务内 SQL 走同一连接。
二、核心抽象:3个接口
| 接口 | 职责 | 关键内容 |
|---|---|---|
PlatformTransactionManager | 事务管理器(顶层) | getTransaction()/commit()/rollback() |
TransactionDefinition | 事务属性定义 | 隔离级别、传播行为、超时、只读、回滚规则 |
TransactionStatus | 事务运行时状态 | 是否新事务、是否已完成、是否存在保存点 |
常见实现类:DataSourceTransactionManager(JDBC / MyBatis 常用)、JpaTransactionManager、HibernateTransactionManager、JtaTransactionManager(分布式)
三、事务传播行为
REQUIREDvsREQUIRES_NEWvsNESTED
- REQUIRED 加入外层事务(一个异常全部回滚);
- REQUIRES_NEW 挂起外层开新事务(内层异常不影响外层已提交);
- NESTED 是保存点式嵌套(内层异常只回滚内层,外层可继续)。
四、隔离级别
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 说明 |
|---|---|---|---|---|
DEFAULT | — | — | — | 使用数据库默认值 |
READ_UNCOMMITTED | 可能 | 可能 | 可能 | 最低级别,几乎不用 |
READ_COMMITTED | 否 | 可能 | 可能 | Oracle 默认,行级锁 |
REPEATABLE_READ | 否 | 否 | 可能(MySQL 借助 MVCC+next-key lock 可避免) | MySQL InnoDB 默认 |
SERIALIZABLE | 否 | 否 | 否 | 最高,串行化,性能差 |
五、@Transactional关键属性
@Transactional( transactionManager = "txManager", // 指定事务管理器(多数据源时) propagation = Propagation.REQUIRED, // 传播行为,默认 REQUIRED isolation = Isolation.DEFAULT, // 隔离级别,默认随数据库 timeout = 10, // 超时(秒),超时自动回滚 readOnly = true, // 只读优化(查询方法建议开启) rollbackFor = Exception.class, // 指定回滚的异常类型 noRollbackFor = SomeException.class // 指定不回滚的异常 )六、事务失效场景
| 场景 | 原因与解决 |
|---|---|
| 同类 this 自调用 | A.methodA()内部调this.methodB(),B 的 @Transactional 不走代理。解决:注入自身代理 / 拆成两个 Bean / 用AopContext.currentProxy() |
| 方法不是 public | Spring 默认只拦截 public 方法,非 public 不报错但事务不生效 |
| 类未被 Spring 管理 | 类没加@Service/@Component,未生成代理 |
| 方法为 final | CGLIB 无法代理 final 方法(无接口时) |
| 异常被 try-catch 吞掉 | 事务感知不到异常,无法回滚。要么抛出,要么手动TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() |
| 抛的是检查异常 | 默认只回滚RuntimeException/Error,检查异常需配rollbackFor |
| 传播行为设置不当 | 如NOT_SUPPORTED / NEVER,或 REQUIRES_NEW 误用导致内层不随外层回滚 |
| 数据库引擎不支持 | MyISAM 等不支持事务,需用 InnoDB |
| 多线程场景 | 连接绑定在父线程 ThreadLocal,子线程拿不到同一事务;REQUIRES_NEW+ 新线程常见误区 |
| 代理对象类型问题 | 类无接口时未启用 CGLIB(老版本需配置proxyTargetClass=true) |
七、底层原理要点
- 代理创建:
@EnableTransactionManagement→ 注册BeanFactoryTransactionAttributeSourceAdvisor,为匹配的方法生成代理(JDK 动态代理 / CGLIB)。 - 拦截器链:
TransactionInterceptor implements MethodInterceptor,在invoke()中执行invokeWithinTransaction()。 - 事务资源绑定:
TransactionSynchronizationManager用ThreadLocal保存当前线程的Connection和事务同步信息 —— 这是 "同一事务同一条连接、传播行为能感知外层事务" 的根本原因。 - 回滚判定:
TransactionInfo记录事务状态,异常时按rollbackOn()规则决定是否回滚。 - 数据源与事务关系:
DataSourceTransactionManager通过DataSourceUtils.getConnection()拿连接并设置autoCommit=false,提交 / 回滚后归还连接。