@Transactional 原理与事务失效场景
一句话
@Transactional 基于 AOP 动态代理,通过 TransactionInterceptor 在方法前后开启/提交/回滚事务。自调用绕过代理、异常被吞、非 public 方法等 6 个场景会导致事务失效。
1. 底层原理
Controller 调用 userService.save() ↓ 实际调用的是 userService 的代理对象(AOP 动态代理生成的) ↓ 代理对象先走 TransactionInterceptor(事务拦截器) ↓ ① 开启事务(从连接池拿连接 → conn.setAutoCommit(false)) ↓ ② 执行真实方法 save() ↓ ③ 正常结束 → commit() 抛异常 → rollback()三个核心角色:
| 角色 | 职责 |
|---|---|
| PlatformTransactionManager | 事务管理器,负责 begin / commit / rollback |
| TransactionInterceptor | 事务拦截器,AOP 切面的具体执行者 |
| @Transactional | 标记注解,告诉 Spring 哪些方法需要事务增强 |
PlatformTransactionManager 由 Spring Boot 自动配置创建(引入 DataSource + spring-tx 依赖即可)。
2. 事务失效的 6 个高频场景
场景 1:自调用(最常考)
@ServicepublicclassUserService{@TransactionalpublicvoidmethodA(){// 操作数据库...this.methodB();// ← this 调用,绕过代理对象}@TransactionalpublicvoidmethodB(){// 操作数据库...}}this.methodB()调用的是原始对象,不是代理对象,TransactionInterceptor 没机会拦截。
解决:
① 把 methodB 抽到另一个 Service 类(走代理) ② 注入自己:@Autowired private UserService self; self.methodB();场景 2:方法不是 public
@Transactionalprivatevoidsave(){// Spring AOP 只拦截 public 方法// 操作数据库}场景 3:异常被 catch 吞掉
@Transactionalpublicvoidsave(){try{// 操作数据库}catch(Exceptione){log.error("出错了",e);// 异常被吞,代理以为正常结束,走 commit}}代理对象只会在方法抛异常时触发 rollback。catch 吞了异常,代理不知道出错了。
解决:catch 里重新 throw,或手动标记回滚:
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();场景 4:异常类型不匹配
@Transactional// 默认 rollbackFor = RuntimeExceptionpublicvoidsave()throwsIOException{thrownewIOException("文件错误");// 检查异常,不回滚}默认只对 RuntimeException 和 Error 回滚。IOException、SQLException 等检查异常不会触发回滚。
解决:@Transactional(rollbackFor = Exception.class)
场景 5:类没交给 Spring 管理
publicclassUserService{// 没有 @Service@Transactionalpublicvoidsave(){// 自己 new 出来的对象没有代理对象}}场景 6:传播级别不对
@Transactional(propagation=Propagation.NOT_SUPPORTED)publicvoidsave(){// 明确说了不支持事务}3. 传播级别 vs 隔离级别
面试容易混的两个概念:
传播级别 propagation:这个方法和已有事务是什么关系 REQUIRED(默认):有事务就加入,没有就新建 REQUIRES_NEW:挂起已有事务,新建独立事务 NOT_SUPPORTED:明确不要事务 SUPPORTS:有事务就加入,没有就非事务执行 MANDATORY:必须在事务中调用,否则抛异常 NEVER:必须不在事务中调用,否则抛异常 NESTED:嵌套事务(子事务回滚不影响父事务) 隔离级别 isolation:事务之间互相隔离的程度 READ_UNCOMMITTED → 脏读 READ_COMMITTED → 不可重复读(MySQL 默认) REPEATABLE_READ → 幻读(InnoDB 默认) SERIALIZABLE → 串行化传播级别影响"事务是否生效",隔离级别影响"并发读写是否安全"。不要混。
4. @Transactional 常用属性
@Transactional(propagation=Propagation.REQUIRED,// 传播级别isolation=Isolation.DEFAULT,// 隔离级别(用数据库默认)timeout=30,// 超时秒数readOnly=false,// 是否只读rollbackFor=Exception.class,// 指定回滚异常类型noRollbackFor=NullPointerException.class// 指定不回滚异常类型)面试回答模板
"@Transactional 底层基于 AOP 动态代理。调用带 @Transactional 注解的方法时, 实际执行的是代理对象,代理对象通过 TransactionInterceptor 在方法前开启事务, 方法正常结束 commit,抛异常 rollback。 事务失效最常见的是自调用:同一个类里 this.method() 绕过了代理对象, TransactionInterceptor 没机会拦截,事务不生效。 其他常见场景包括:方法不是 public、异常被 catch 吞掉、 异常类型不匹配(默认只回滚 RuntimeException)、 类没交给 Spring 管理、传播级别设为 NOT_SUPPORTED。 这里要注意传播级别和隔离级别是两个不同概念: 传播级别控制方法和已有事务的关系,隔离级别控制并发读写的隔离程度。"参考来源
- Day11-Spring 与 SpringBoot
01_Raw【原始素材】/面试资料-B站博主/Spring篇(13题) - 飞书云文档.md
