目录
[1. 方法不是 public(最基础)](#1. 方法不是 public(最基础))
[2. 同类内方法调用(this 调用,不走代理,最高频)](#2. 同类内方法调用(this 调用,不走代理,最高频))
[3. 异常类型不对:捕获了异常 / 抛出受检异常,默认不回滚](#3. 异常类型不对:捕获了异常 / 抛出受检异常,默认不回滚)
[场景 3.1 try-catch 捕获异常,没有抛出](#场景 3.1 try-catch 捕获异常,没有抛出)
[场景 3.2 抛出受检异常(Exception),没有指定 rollbackFor](#场景 3.2 抛出受检异常(Exception),没有指定 rollbackFor)
[4. 数据库引擎不支持事务](#4. 数据库引擎不支持事务)
[5. 传播行为配置错误](#5. 传播行为配置错误)
[6. 多线程场景事务失效](#6. 多线程场景事务失效)
[7. 注解加在类上,但方法是 final/static](#7. 注解加在类上,但方法是 final/static)
[8. 事务超时、只读配置误用](#8. 事务超时、只读配置误用)
[9. 嵌套事务场景:内部异常被 catch,外部事务不回滚(传播机制)](#9. 嵌套事务场景:内部异常被 catch,外部事务不回滚(传播机制))
[Q1:@Transactional(rollbackFor = Exception.class) 为什么要写?](#Q1:@Transactional(rollbackFor = Exception.class) 为什么要写?)
[Q2:什么是 rollback-only?](#Q2:什么是 rollback-only?)
[Q3:Spring 事务底层用什么保存事务信息?](#Q3:Spring 事务底层用什么保存事务信息?)
[Q4:两种代理 JDK 动态代理 vs CGLIB](#Q4:两种代理 JDK 动态代理 vs CGLIB)
Spring 事务底层是AOP 动态代理。只有被代理对象调用 public 方法,并且通过代理对象执行方法,事务才生效。
核心一句话:事务是代理对象增强出来的能力;如果不走代理,事务直接失效。
1. 方法不是 public(最基础)
现象
@Transactional 加在 private /protected/default 方法上,事务不生效。
原理
Spring AOP 只能拦截 public 方法;非 public 方法不会生成代理增强逻辑。
java
// ❌ 失效
@Transactional
private void testTx(){
userMapper.insert(...);
int i = 1/0;
}
✅ 解决:改成 public。
2. 同类内方法调用(this 调用,不走代理,最高频)
现象
同一个类中,无事务的方法 A,直接调用本类带 @Transactional 的方法 B,事务失效。
java
@Service
public class UserService {
public void A() {
// this是原始对象,不是代理对象!!
this.B();
}
@Transactional
public void B() {
userMapper.insert(...);
int i = 1/0; // 抛异常,不会回滚!
}
}
原理
this.B() 调用的是原始对象的方法,不是 Spring 生成的代理对象。
代理对象的事务增强逻辑没有执行。
✅ 解决:
- 自己注入自己
@Autowired private UserService self;,用self.B() - 使用 AopContext.currentProxy () 获取代理对象(需要开启
exposeProxy=true) - 拆分成两个类
3. 异常类型不对
捕获了异常 / 抛出受检异常,默认不回滚
Spring 事务默认只对 RuntimeException 和 Error 回滚。
无 rollbackFor:只认
RuntimeException、Error;
场景 3.1 try-catch 捕获异常,没有抛出
java
@Transactional
public void test(){
try{
userMapper.insert(...);
int i=1/0;
}catch (Exception e){
// 吃掉异常,代理感知不到异常,不会触发回滚
log.error("err",e);
}
}
✅ 解决:catch 之后重新抛出异常 throw e;
java
@Transactional
public void test(){
try{
userMapper.insert(...);
int i=1/0;
}catch (Exception e){
log.error("err",e);
// 重新抛出异常,让AOP代理捕获到,触发事务回滚
throw e;
//优化:或者包装为运行时异常抛出
//throw new RuntimeException("业务操作失败", e);
}
}
补充说明
- 原理:
@Transactional的增强逻辑是在方法执行完毕后判断是否抛出异常;只有方法抛出异常,Spring 才会标记事务回滚。catch 住不抛,代理认为方法正常执行,直接提交事务。 - 优化写法(很多业务不会直接 throw 原始 e,包装自定义异常):
重要面试小坑
如果抛出的是受检异常(Exception) ,即使 throw e,默认也不会回滚!
示例(❌ 这样写依然不回滚):
java
@Transactional
public void test() throws Exception{
try{
userMapper.insert(...);
throw new Exception("受检异常");
}catch (Exception e){
log.error("err",e);
throw e; // Exception是受检异常,Spring默认不回滚
}
}
✅ 需要指定 rollbackFor
rollbackFor=Exception.class:只要抛 Exception 及其子类就回滚。
java
@Transactional(rollbackFor = Exception.class)
public void test() throws Exception{
try{
userMapper.insert(...);
throw new Exception("受检异常");
}catch (Exception e){
log.error("err",e);
throw e;
}
}
场景 3.2 抛出受检异常(Exception),没有指定 rollbackFor
java
// ❌ 默认不会回滚!
@Transactional
public void test() throws Exception{
userMapper.insert(...);
throw new Exception("受检异常");
}
✅ 解决:指定回滚类型
java
@Transactional(rollbackFor = Exception.class)
面试一句话总结
- 异常必须抛给 AOP 代理,才会进入回滚判断;
- 无 rollbackFor:只认
RuntimeException、Error;rollbackFor=Exception.class:只要抛 Exception 及其子类就回滚。
4. 数据库引擎不支持事务
比如 MySQL 使用 MyISAM 引擎,MyISAM 不支持事务,无论怎么加注解都没用。
✅ 解决:改成 InnoDB。
5. 传播行为配置错误
举典型:REQUIRES_NEW、SUPPORTS、NOT_SUPPORTED 例:@Transactional(propagation = Propagation.NOT_SUPPORTED):以非事务方式运行; 或者 REQUIRES_NEW 嵌套事务,内部异常被捕获,外部不会回滚。
面试常问:REQUIRED(默认):有事务就加入,没有新建事务。
6. 多线程场景事务失效
java
@Transactional
public void test(){
new Thread(()->{
userMapper.insert(...);
int i=1/0;
}).start();
}
原理
Spring 事务绑定在当前线程 ThreadLocal。新开线程是全新 Thread,事务上下文不会传递,子线程操作属于独立事务。 子线程抛异常,不会回滚主线程的事务。
7. 注解加在类上,但方法是 final/static
- final 方法:无法被子类重写,CGLIB 无法增强,事务失效
- static 方法:静态属于类,不属于实例,代理无法增强
java
@Transactional
public final void test(){ ... } // ❌失效
8. 事务超时、只读配置误用
@Transactional(readOnly = true):只读事务,不能执行增删改,写操作会抛异常。
9. 嵌套事务场景
内部异常被 catch,外部事务不回滚(传播机制)
java
@Transactional
public void outer() {
try {
inner();
} catch (Exception e) {
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void inner(){
throw new RuntimeException();
}
inner 是独立事务,inner 回滚;outer 捕获异常,outer 事务正常提交。
如果是默认 REQUIRED,内部抛异常,标记事务回滚点,即使外层 catch,提交时依然会报:
Transaction rolled back because it has been marked as rollback-only
精简总结
Spring 事务基于 AOP 代理,事务失效主要原因:
- 方法非 public;final/static 方法无法代理
- 同类内 this 调用,不走代理对象
- 异常被 try-catch 吃掉,或者抛出受检异常,没配置 rollbackFor
- 数据库引擎不支持事务(MyISAM)
- 多线程:事务存 ThreadLocal,子线程无法继承事务上下文
- 事务传播行为配置错误
- readOnly=true 执行写操作
面试官高频追问
Q1:@Transactional(rollbackFor = Exception.class) 为什么要写?
Spring 事务默认只对 RuntimeException、Error 回滚。
Exception 包含受检异常,如果业务抛出 IOException 等受检异常,不加 rollbackFor 不会回滚。
Q2:什么是 rollback-only?
rollback-only 是事务的一个标记。
一旦这个标记被打上,代表这个事务只能回滚,不能提交。
哪怕外层代码捕获了异常,最后提交的时候 Spring 依然强制回滚,并且抛出异常:
Transaction rolled back because it has been marked as rollback-only。rollback-only 标记,只有在「发生了满足回滚规则的异常」的时候才会被打上。不会凭空出现。
打上标记之后:不管后续代码有没有异常、代码是否正常跑完,这个事务都禁止提交,最终一定会回滚。
不是 "事务没出错也会回滚";而是曾经出过一次触发回滚的异常,给事务打上标记,就算后面代码正常,也不能提交了。
前提:同一个事务(传播行为 REQUIRED,默认)场景演示(最经典)
java@Service public class TxDemoService { // 外层方法 @Transactional public void outer() { try { inner(); } catch (Exception e) { log.error("捕获内层异常", e); // 注意:这里catch住了,没有向外抛! } } // 内层方法,REQUIRED,会加入外层的同一个事务 @Transactional public void inner() { // 抛出运行时异常 throw new RuntimeException("内层业务异常"); } }执行流程拆解
- 调用
outer(),开启事务。outer调用inner();inner 是 REQUIRED,加入当前事务,不会新建事务。- inner 抛出
RuntimeException,Spring 捕获异常,给当前整个事务打上 rollback-only 标记。- 异常回到 outer,被 try-catch 捕获,没有继续向外抛出。
- outer 方法执行完毕,准备提交事务。
- Spring 检查事务状态:发现有
rollback-only标记。拒绝提交,强制回滚,并且抛出Transaction rolled back because it has been marked as rollback-only核心关键点
- 虽然外层 catch 吃掉异常,方法正常返回,但是事务标记已经不可逆。
- rollback-only 是绑定在事务上的标记,不是绑定在方法上。同一个事务内任何一个地方抛出触发回滚的异常,整个事务就被标记。
- 这个标记一旦打上,不能清除,只能回滚。
❗ 容易踩坑区分:REQUIRES_NEW 不会出现 rollback-only
java@Transactional public void outer() { try { inner(); } catch (Exception e) { log.error("捕获内层异常", e); } } // REQUIRES_NEW 新建独立事务 @Transactional(propagation = Propagation.REQUIRES_NEW) public void inner() { throw new RuntimeException("异常"); }流程:
- outer 开启事务
- inner 触发 REQUIRES_NEW:挂起外层事务,新建独立事务
- inner 抛异常 → inner 自己的事务回滚。
- inner 异常抛到 outer,被 catch。内层事务已经结束,不会给外层事务打 rollback-only 标记
- outer 继续执行,最后正常提交。
✅ 内外是两个独立事务,互不影响,不会 rollback-only。
追问:什么时候会打上 rollback-only?
在同一个事务里面,方法抛出满足回滚条件的异常(默认 RuntimeException/Error,或者 rollbackFor 指定的异常),Spring 就会给当前事务打上 rollback-only。
面试题回答模板
rollback-only 是 Spring 事务的标记。当多个方法使用 REQUIRED 共用同一个事务,其中一个方法抛出回滚异常,Spring 会给整个事务打上 rollback-only 标记。
即使上层方法捕获异常不向外抛出,事务也不能提交,最终强制回滚,并抛出
Transaction rolled back because it has been marked as rollback-only。本质:同一个事务,一损俱损。
怎么避免 rollback-only 异常?
两种方案:
- 如果希望内层异常不影响外层提交:内层使用
REQUIRES_NEW,新开独立事务(注意:会挂起事务,有性能开销)。- 业务上,如果内层异常不需要回滚整个事务:不要在内层抛出会触发回滚的异常,改用业务返回码,不抛异常。
Q3:Spring 事务底层用什么保存事务信息?
ThreadLocal,把事务状态、连接信息绑定到当前线程。所以多线程无法共享事务。
Q4:两种代理 JDK 动态代理 vs CGLIB
- JDK 代理:目标必须实现接口,代理接口方法
- CGLIB:继承目标类,重写方法;类不能是 final,方法不能 final
面试真题
题目
下面代码事务会不会生效?为什么?
java
@Service
public class OrderService {
public void createOrder() {
saveOrder();
}
@Transactional
public void saveOrder() {
orderMapper.insert();
int a = 1/0;
}
}
参考答案
事务失效。
原因:createOrder 通过 this 调用本类的 saveOrder,this 是原始对象,不是 Spring AOP 代理对象,事务增强逻辑不会执行,异常不会回滚。