Spring事务失效场景#
一句话答案#
@Transactional 失效的根因是绕过了代理或事务拦截器没有按预期工作,常见八种场景:自调用、非 public、异常被 catch、异常类型不匹配、传播行为错误、非 Spring 管理的类、多线程、数据库不支持事务。
核心要点
八种事务失效场景#
1. 自调用(同类方法调用)— 最高频#
@Service
public class OrderService {
public void createOrder() {
this.updateStock(); // 直接调用,不经过代理 → 事务失效
}
@Transactional
public void updateStock() { ... }
}java原因: this.updateStock() 是直接调用,没有经过代理对象,AOP 拦截不到。
解决: 注入自己 @Autowired OrderService self; 然后调 self.updateStock(),或用 AopContext.currentProxy()。
2. 非 public 方法#
原因: Spring 事务拦截器(TransactionInterceptor)主动跳过非 public 方法。
3. 异常被 catch 吞掉#
@Transactional
public void transfer() {
try {
deduct(); add();
} catch (Exception e) {
log.error("转账失败", e); // 异常被吞,事务不回滚
}
}java解决: catch 后 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(),或重新抛出。
4. 异常类型不匹配#
@Transactional // 默认只回滚 RuntimeException 和 Error
public void process() throws IOException {
throw new IOException("文件不存在"); // 受检异常,不回滚!
}java解决: @Transactional(rollbackFor = Exception.class)。
5. 传播行为配置错误#
Propagation.NOT_SUPPORTED / NEVER 会以非事务方式执行。
6. 非 Spring 管理的类#
没有 @Service/@Component 注解 → 不是 Spring Bean → 事务无效。
7. 多线程场景#
事务信息存在 ThreadLocal 中,新线程无法获取原事务上下文。
8. 数据库引擎不支持#
MyISAM 不支持事务,只有 InnoDB 支持。
深挖:从底层机制理解失效根因#
事务的本质:
TransactionInterceptor→DataSourceTransactionManager拿一条Connection、setAutoCommit(false),再由TransactionSynchronizationManager把这条连接绑到当前线程的 ThreadLocal;业务里所有 SQL(MyBatis/JDBC)都从 ThreadLocal 取同一条连接,最后统一 commit/rollback。理解这条主线,多数失效场景一眼看穿:
| 失效场景 | 底层根因 |
|---|---|
| 自调用 | this.method() 不走代理 → TransactionInterceptor 没被触发 → 连开事务的入口都没进 |
| 非 public | 拦截器 computeTransactionAttribute 对非 public 方法返回 null,主动跳过 |
| 多线程 | 连接绑在发起线程的 ThreadLocal;新线程取不到,自己新开连接(autocommit=true),与外层无关 |
| catch 吞异常 | 拦截器靠捕获到异常才调 rollback(),异常被吞 → 拦截器以为成功 → commit |
| 异常类型不匹配 | rollbackOn() 默认只对 RuntimeException/Error 返回 true,受检异常被当作正常 → commit |
一句话锚定: 失效要么是代理没介入(自调用/非 public/非 Bean),要么是拦截器拿不到回滚信号(吞异常/异常类型不匹配),要么是ThreadLocal 连接不共享(多线程)。
验证事务是否生效#
boolean isActive = TransactionSynchronizationManager.isActualTransactionActive();
log.info("事务是否激活: {}", isActive);java面试回答(2分钟版)
@Transactional 失效的根因是代理没有介入。最常见的是自调用——同类内 this.method() 不经过代理对象所以事务不生效,解决方法是注入自身或用 AopContext。第二个是非 public 方法,Spring 事务拦截器只处理 public。第三个是异常被 catch 吞掉了,Spring 靠异常触发回滚,catch 了就看不到。第四个是异常类型不匹配,默认只回滚 RuntimeException,受检异常需要 rollbackFor=Exception.class。还有传播行为配置为 NOT_SUPPORTED 会以非事务方式执行,多线程场景事务信息在 ThreadLocal 中不跨线程。
追问与易错
追问方向:
- “你项目中遇到过事务失效吗?”→ 结合实际经历讲
- “自调用怎么解决?”→ 注入自身 / AopContext / 拆分到不同 Service
- “rollbackFor 默认值是什么?”→ RuntimeException + Error,不包含受检异常
易错点:
- ❌ “加了 @Transactional 就有事务”——至少有八种失效场景
- ❌ “@Transactional 可以加在 private 方法上”——不会报错但不生效
- ❌ “catch 了异常重新 throw 就行”——throw 的必须是 rollbackFor 包含的类型