Spring事务管理
本篇定位
数据库事务是 异常 与 AOP 在 Spring 里的一次合体:@Transactional 本质是个 AOP 代理,异常配合回滚。本篇讲清三件事:传播行为、隔离级别、失效的 6 个场景(面试最爱挖坑的那部分)。前置:Spring AOP。
一、@Transactional 的声明式事务
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
orderMapper.insert(order); // ① 插入订单
stockService.deduct(order.getSkuId(), 1); // ② 扣库存
} // ③ 方法正常返回 → 提交;抛 RuntimeException → 回滚
}
三个默认行为先记住:
- 默认只回滚 RuntimeException 和 Error,受检异常(Checked Exception,如 IOException)不回滚——要回滚必须在注解上写
@Transactional(rollbackFor = Exception.class); - 事务由 Spring 的 AOP 代理管理,方法内
this.createOrder()自调用时代理不生效(见第六节); - 事务只对"通过 Spring 容器拿到的 Bean"生效——自己
new OrderService()是没有事务的。
二、事务传播行为(Propagation)
@Transactional(propagation = Propagation.REQUIRED) 控制"方法 A 调方法 B,B 要不要开新事务":
| 传播行为 | 行为 | 典型场景 |
|---|---|---|
| REQUIRED(默认) | 有事务就加入,没事务就新建 | 绝大多数业务 |
| REQUIRES_NEW | 无论如何挂起当前事务,开新事务 | 日志记录(主事务回滚也不影响日志) |
| NESTED | 嵌套事务,子事务可独立回滚到保存点 | 部分失败可单独回滚 |
| SUPPORTS | 有就加入,没有就非事务运行 | 只读查询 |
| NOT_SUPPORTED | 挂起当前事务,非事务运行 | 不需要事务的耗时操作 |
| MANDATORY | 必须已有事务,否则抛异常 | 强制必须在事务里跑 |
| NEVER | 必须在非事务下运行,否则抛异常 | 禁止事务 |
必考两个:
- REQUIRED vs REQUIRES_NEW:REQUIRED 加入现有事务,B 抛异常会导致 A 一起回滚;REQUIRES_NEW 开独立事务,B 提交/回滚都不影响 A(B 成功则立即提交,即使 A 后来回滚);
- NESTED vs REQUIRES_NEW:NESTED 用保存点,子事务回滚不脏掉外层;REQUIRES_NEW 是完全隔离的两个普通事务。
三、隔离级别与并发问题
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ_UNCOMMITTED | ❌存在 | ❌ | ❌ |
| READ_COMMITTED | ✅解决 | ❌ | ❌ |
| REPEATABLE_READ(MySQL 默认) | ✅ | ✅ | 基本解决(MVCC) |
| SERIALIZABLE | ✅ | ✅ | ✅ |
配置:@Transactional(isolation = Isolation.READ_COMMITTED)。常识:隔离级别是数据库的核心能力,Spring 只是把配置透传给数据库连接;隔离级别越高并发越低,默认别乱动,MySQL 的 RR + MVCC 已经解决了大部分问题。
四、事务失效的 6 个场景(面试高发)
- 方法不是 public:
@Transactional依赖动态代理,JDK 代理只能拦 public 方法;private/protected 方法不会生效——Spring 框架会告警但不会报错; - 未被 Spring 管理:自己
new OrderService()创建的对象没有代理,注解是死的; - 自调用:方法 A 内部
this.methodB(),this是原始对象不是代理,B 上的@Transactional不生效(经典坑,需通过注入自身或 AopContext 或拆成两个 Bean 解决); - 异常被吞:方法内
try { ... } catch (Exception e) { log.error(e); }把异常吃掉了,代理感知不到异常 → 不回滚。正确做法是 catch 里throw或显式TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); - 抛的是受检异常:默认不回滚 Checked Exception,见第一节第 1 点;
- 数据库引擎不支持事务:MyISAM 表不支持事务,Spring 再配也没用——InnoDB 才支持(详见 容器块 外的 MySQL 笔记)。
五、事务与批量/大事务
- 大事务拆小:循环里每次
@Transactional方法各自开事务,避免一个长事务锁住大量行(for循环内调代理方法,注意自调用问题); - 只读优化:
@Transactional(readOnly = true)告诉引擎走 MVCC 快照读、避免不必要的锁; - 事务里别做耗时 IO:长时间持锁导致锁等待、死锁概率上升。
六、高频面试题速答
- @Transactional 失效的场景? 见第四节的 6 个场景,重点答前三个(非 public、未代理、自调用)。
- 默认回滚什么异常? RuntimeException 和 Error,受检异常不回滚,需
rollbackFor显式指定。 - REQUIRED 和 REQUIRES_NEW 区别? 前者加入当前事务同生共死,后者挂起当前事务开新事务独立提交。
- 事务隔离级别默认多少? 跟随数据库默认(MySQL 是 REPEATABLE_READ),Spring 不设置时就是数据库默认值。
勾连
- AOP 代理如何包裹事务方法:Spring AOP
- 异常体系与回滚关系:异常体系
- Bean 生命周期里 @Transactional 的代理生成时机:Bean生命周期与三级缓存
⬅️ 04-Bean生命周期与三级缓存 🏠 00-Java ➡️ 00-语法与鱼皮总览
💬 评论