前言
Spring事务默认只对 RuntimeException 和 Error 回滚,对 Checked Exception 不回滚;如果方法抛出 Checked Exception 且未配置 rollbackFor,事务会被正常提交。
Spring事务只在“代理对象调用方法时”生效,类内部 self-invocation(自调用)会绕过代理。
一、try/catch 里把异常“吃掉了,事务不会回滚
例子:
@Transactional
@GetMapping("/test1")
public void create() {
try {
orderMapper.insert(new order(1L,1L));
// 模拟异常
int a = 1 / 0;
} catch (Exception e) {
System.out.println("异常被捕获了:" + e.getMessage());
}
}
解决:
1.直接不 catch
@Transactional
public void create() {
orderMapper.insert(new order(1L,1L));
int a = 1 / 0; // 自动回滚
}
2.重新抛出异常
@Transactional
public void create() {
try {
orderMapper.insert(new order(1L,1L));
int a = 1 / 0;
} catch (Exception e) {
System.out.println("记录日志");
throw new RuntimeException(e); // ⭐关键:必须抛出去
}
}
3.手动标记回滚
@Transactional
public void create() {
try {
orderMapper.insert(new order(1L,1L));
int a = 1 / 0;
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus()
.setRollbackOnly();
}
}
二、方法抛出 Checked Exception 且未配置 rollbackFor,事务不会回滚
例子:
@Transactional
public void test() throws Exception {
throw new Exception("编译时异常");
}
❗关键原因
Spring 的逻辑是:
“我只会对 RuntimeException 感兴趣”
你的 Exception 是:
Checked Exception → Spring 默认忽略回滚
解决:
方案1:强制所有异常都回滚(推荐)
@Transactional(rollbackFor = Exception.class)
public void test() throws Exception {
throw new Exception("库存不足");
}
方案2:统一用 RuntimeException
@Transactional
public void test() {
throw new RuntimeException("库存不足");
}
三:@Transactional 在同类方法调用会失效
例子:
@Service
public class OrderService {
public void createOrder() {
insertOrder(); // ❌ 同类调用
}
@Transactional
public void insertOrder() {
orderMapper.insert(...);
int a = 1 / 0; // 期待回滚
}
}
实际发生什么?
createOrder() → 直接调用 insertOrder()
❌ 没经过 Spring 代理
❌ @Transactional 不生效
Spring事务基于AOP代理实现,仅在通过代理对象调用方法时生效;同类内部方法调用属于 self-invocation,不会经过代理,因此事务不会生效。
正确解决方案(重点)
✔ 方案1:拆服务(最推荐)
@Service
public class OrderService {
@Autowired
private OrderTxService orderTxService;
public void createOrder() {
orderTxService.insertOrder();
}
}
@Service
public class OrderTxService {
@Transactional
public void insertOrder() {
orderMapper.insert(...);
}
}
方案2:通过代理调用自己(不推荐但可用)
@Autowired
private ApplicationContext context;
public void createOrder() {
OrderService proxy = context.getBean(OrderService.class);
proxy.insertOrder(); // ✔ 走代理
}
四:private 方法导致事务失效
@Transactional
private void b() {
}
❗原因
Spring AOP 无法代理 private 方法
只能代理 public/protected
总结
Spring 的声明式事务基于 AOP 实现。@Transactional 只是事务声明,Spring 会通过 JDK 动态代理或 CGLIB 为目标 Bean 创建代理对象。方法调用时先进入事务拦截器,由事务拦截器负责开启事务、提交事务和回滚事务。因此事务能否生效的关键在于是否经过代理对象,这也是同类调用、private 方法、final 方法等场景事务失效的根本原因。
面试生产环境中事务怎么使用?
1.统一配置 rollbackFor = Exception.class
2.非核心业务拆解,用异步保证一致性
3.统一异常体系:继承RuntimeException重写异常类
4.事前压测
@作者:加辣椒了吗?
简介:憨批大学生一枚,喜欢在博客上记录自己的学习心得,也希望能够帮助到你们!