Spring 事务失效,是面试里非常高频的问题。核心要先记住一句话:
Spring 事务本质是基于 AOP 代理实现的。
事务失效,往往不是“事务功能坏了”,而是没有经过代理,或者回滚条件没满足。
我按常见场景给你系统整理。
一、同类内部调用,导致事务失效
这是最经典、最常考的场景。
场景
同一个类里面,一个方法直接调用另一个加了 @Transactional 的方法:
@Service
public class OrderService {
public void createOrder() {
saveOrder(); // 直接调用
}
@Transactional
public void saveOrder() {
// 数据库操作
}
}
为什么失效
Spring 事务靠代理生效。
外部调用 saveOrder() 时,调用的是代理对象,事务能织入。
但类内部 this.saveOrder() 或直接 saveOrder(),走的是当前对象本身,不经过代理,所以事务不会生效。
解决方式
- 把事务方法拆到另一个 Spring Bean 中
- 或通过代理对象调用自己
- 或使用
AopContext.currentProxy()获取代理对象再调
二、方法不是 public,事务可能失效
场景
@Transactional
private void save() {
}
或者:
@Transactional
protected void save() {
}
为什么失效
Spring 默认基于代理实现事务,通常只对 public 方法进行代理增强。
如果方法是 private、protected,很多情况下事务拦截不到。
尤其是 private 方法,子类都无法重写,CGLIB 也没法增强。
结论
事务方法尽量写成 public。
三、方法被 final 修饰,可能导致事务失效
场景
@Transactional
public final void saveOrder() {
}
为什么失效
如果 Spring 使用的是 CGLIB 代理,它是通过生成子类、重写方法来实现增强的。
final 方法不能被重写,所以事务逻辑无法织入。
补充
如果是 JDK 动态代理,代理的是接口方法,影响没那么直接;
但面试里一般回答:
final 方法会影响基于代理的事务增强,尽量不要这样写。
四、Bean 没有交给 Spring 管理
场景
OrderService orderService = new OrderService();
orderService.saveOrder();
为什么失效
@Transactional 只有在对象是 Spring 容器中的 Bean,并且通过 Spring 代理对象调用时才生效。
你自己 new 出来的对象,不归 Spring 管,自然也不会有事务代理。
结论
带事务的方法所在类,必须交给 Spring 容器管理。
五、异常被吞掉了,导致不回滚
场景
@Transactional
public void saveOrder() {
try {
// 数据库操作
int i = 1 / 0;
} catch (Exception e) {
System.out.println("出错了");
}
}
为什么失效
事务拦截器判断是否回滚,通常是看方法有没有把异常抛出去。
如果你把异常 catch 住了,又没有继续抛出,Spring 会认为方法正常结束,于是提交事务,而不是回滚。
解决方式
- catch 后重新抛出异常
- 或手动标记回滚
例如:
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
六、抛出的异常类型不对,默认不会回滚
场景
@Transactional
public void saveOrder() throws Exception {
throw new Exception("test");
}
为什么失效
Spring 默认规则是:
- 遇到
RuntimeException和Error才回滚 - 遇到受检异常
Exception默认不回滚
所以像:
IOExceptionSQLExceptionException
这类受检异常,如果不额外配置,事务可能不会回滚。
解决方式
@Transactional(rollbackFor = Exception.class)
面试要点
这句一定要会背:
Spring 默认只对运行时异常和 Error 回滚,对受检异常默认不回滚。
七、数据库引擎不支持事务
场景
比如 MySQL 表使用的是 MyISAM,不是 InnoDB。
为什么失效
Spring 只能帮你控制事务边界,但真正提交和回滚,是数据库事务机制在做。
如果底层存储引擎本身不支持事务,那 Spring 再怎么加 @Transactional 也没用。
结论
MySQL 里要确认表引擎是 InnoDB。
八、事务传播行为设置不当
场景
比如外层方法有事务,内层方法配置了不合适的传播行为:
@Transactional
public void methodA() {
methodB();
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void methodB() {
}
为什么失效
不同传播行为决定方法如何加入或新建事务。
如果传播行为设置不当,可能导致你以为它在事务里执行,实际上没有。
常见传播行为影响:
REQUIRED:有事务就加入,没有就新建REQUIRES_NEW:挂起当前事务,自己新开事务SUPPORTS:有事务就加入,没有就非事务执行NOT_SUPPORTED:不支持事务,始终非事务执行NEVER:如果当前有事务就报错
面试点
很多“事务没按预期回滚”,其实是 传播机制理解错了。
九、多线程场景下事务失效
场景
@Transactional
public void saveOrder() {
new Thread(() -> {
// 数据库操作
}).start();
}
为什么失效
Spring 事务通常是绑定在线程上的,底层通过 ThreadLocal 管理当前事务资源。
你在新线程里执行数据库操作,这个线程拿不到原线程的事务上下文,所以事务不会继承过去。
结论
Spring 事务默认不能跨线程传播。
十、@Transactional 加在 static 方法上失效
为什么失效
Spring AOP 代理的是对象方法,而 static 方法属于类,不属于对象实例,无法被代理增强。
结论
事务不要加在 static 方法上。
十一、@Transactional 加在接口、实现类位置不当,可能不生效
这个场景要看具体代理方式和配置,但面试里可以这么答:
- 一般建议把
@Transactional写在实现类上 - 不要过度依赖写在接口上
- 尤其在复杂代理场景下,可能出现识别不到的问题
这样回答更稳。
十二、手动调用后置初始化或绕过代理链
比如某些特殊场景里,你拿到的不是 Spring 容器中的代理对象,而是目标对象本身,或者通过某种方式绕过了代理链,这也会导致事务失效。
本质上还是同一个原因:
没有经过 Spring 事务代理。
十三、只读事务里执行写操作,可能表现异常
场景
@Transactional(readOnly = true)
public void saveOrder() {
// insert/update/delete
}
为什么会出问题
readOnly = true 主要是告诉框架/数据库这是只读事务。
不同数据库和 ORM 实现下,可能会:
- 不报错但性能/行为异常
- flush 机制异常
- 写入不符合预期
这不一定叫“事务完全失效”,但属于事务配置不当导致的常见坑。
十四、异常发生的位置不在事务拦截范围内
比如事务方法已经执行完并提交了,后面别的逻辑再抛异常,自然无法回滚已经提交的事务。
所以要注意:
只有事务方法执行期间抛出的、并被事务拦截器感知到的异常,才会触发回滚。
十五、使用了错误的数据源或事务管理器
在多数据源项目里,经常有这种问题:
- 业务操作走的是数据源 A
- 配置的事务管理器却管理的是数据源 B
结果看起来加了事务,实际上没有管到真正执行 SQL 的连接。
这个在实际项目里很常见,特别是:
- 多数据源
- 读写分离
- 分库分表
- 多事务管理器并存
十六、@Transactional 标注在非 Spring 托管调用链之外的方法上
比如一个事务方法被 private、内部调用、工具类调用、异步回调等方式绕开代理,本质都是:
- 注解写了
- 但执行时没走代理
所以你可以把很多失效场景归纳成两大类:
- 事务代理没有生效
- 回滚规则没有生效
十七、最常见的几个失效原因,面试优先说这些
- 同类内部调用,绕过代理
- 方法不是 public
- 异常被捕获吞掉,没有抛出
- 抛出的是受检异常,默认不回滚
- Bean 不是 Spring 容器管理的
- 多线程场景,事务不能跨线程传播
- 传播行为配置不当
- 数据库本身不支持事务
final/static/private等导致代理失效- 多数据源下事务管理器配错
十八、背诵版
Spring 事务失效的常见场景,本质上主要分为两类:一类是没有经过 Spring 的事务代理,另一类是虽然进了事务,但没有满足回滚条件。最典型的情况包括:同类内部方法调用导致绕过代理;事务方法不是 public;方法被 final、static、private 修饰,导致无法代理;Bean 不是 Spring 容器管理;异常被 catch 住没有继续抛出;抛出的是受检异常而不是运行时异常,默认不会回滚;事务传播行为配置不当;多线程场景下事务无法跨线程传播;数据库引擎本身不支持事务;以及多数据源场景下事务管理器配置错误。归根到底,Spring 事务依赖 AOP 代理和正确的回滚规则,只要代理链断了,或者异常没有按事务规则传递出去,事务就可能失效。
十九、万能总结
Spring 事务失效,最核心的原因有两个:第一,没有走到 Spring 的代理对象,比如同类内部调用、方法不可代理、对象不是 Spring 管理;第二,虽然进了事务,但没有触发回滚,比如异常被吞掉、抛出的是受检异常、传播行为配置不当等。