--- title: "05-Spring事务管理" created: 2026-09-03 tags: - Java --- # Spring事务管理 > [!note] 本篇定位 > 数据库事务是 [[04-异常体系|异常]] 与 AOP 在 Spring 里的一次合体:`@Transactional` 本质是个 AOP 代理,异常配合回滚。本篇讲清三件事:传播行为、隔离级别、**失效的 6 个场景**(面试最爱挖坑的那部分)。前置:[[03-Spring AOP(面向切面编程)|Spring AOP]]。 ## 一、@Transactional 的声明式事务 ```java @Service public class OrderService { @Transactional public void createOrder(Order order) { orderMapper.insert(order); // ① 插入订单 stockService.deduct(order.getSkuId(), 1); // ② 扣库存 } // ③ 方法正常返回 → 提交;抛 RuntimeException → 回滚 } ``` 三个默认行为先记住: 1. **默认只回滚 RuntimeException 和 Error**,受检异常(Checked Exception,如 IOException)**不回滚**——要回滚必须在注解上写 `@Transactional(rollbackFor = Exception.class)`; 2. **事务由 Spring 的 AOP 代理管理**,方法内 `this.createOrder()` 自调用时代理不生效(见第六节); 3. 事务只对"通过 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 个场景(面试高发) 1. **方法不是 public**:`@Transactional` 依赖动态代理,JDK 代理只能拦 public 方法;private/protected 方法不会生效——Spring 框架会告警但不会报错; 2. **未被 Spring 管理**:自己 `new OrderService()` 创建的对象没有代理,注解是死的; 3. **自调用**:方法 A 内部 `this.methodB()`,`this` 是原始对象不是代理,B 上的 `@Transactional` 不生效(经典坑,需通过注入自身或 AopContext 或拆成两个 Bean 解决); 4. **异常被吞**:方法内 `try { ... } catch (Exception e) { log.error(e); }` 把异常吃掉了,代理感知不到异常 → 不回滚。正确做法是 catch 里 `throw` 或显式 `TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()`; 5. **抛的是受检异常**:默认不回滚 Checked Exception,见第一节第 1 点; 6. **数据库引擎不支持事务**:MyISAM 表不支持事务,Spring 再配也没用——InnoDB 才支持(详见 [[00-容器总览|容器块]] 外的 MySQL 笔记)。 ## 五、事务与批量/大事务 - **大事务拆小**:循环里每次 `@Transactional` 方法各自开事务,避免一个长事务锁住大量行(`for` 循环内调代理方法,注意自调用问题); - **只读优化**:`@Transactional(readOnly = true)` 告诉引擎走 MVCC 快照读、避免不必要的锁; - **事务里别做耗时 IO**:长时间持锁导致锁等待、死锁概率上升。 ## 六、高频面试题速答 1. **@Transactional 失效的场景?** 见第四节的 6 个场景,重点答前三个(非 public、未代理、自调用)。 2. **默认回滚什么异常?** RuntimeException 和 Error,受检异常不回滚,需 `rollbackFor` 显式指定。 3. **REQUIRED 和 REQUIRES_NEW 区别?** 前者加入当前事务同生共死,后者挂起当前事务开新事务独立提交。 4. **事务隔离级别默认多少?** 跟随数据库默认(MySQL 是 REPEATABLE_READ),Spring 不设置时就是数据库默认值。 ## 勾连 - AOP 代理如何包裹事务方法:[[03-Spring AOP(面向切面编程)|Spring AOP]] - 异常体系与回滚关系:[[04-异常体系|异常体系]] - Bean 生命周期里 @Transactional 的代理生成时机:[[04-Bean生命周期与三级缓存|Bean生命周期与三级缓存]] --- ⬅️ [[04-Bean生命周期与三级缓存|04-Bean生命周期与三级缓存]] 🏠 [[00-Java|00-Java]] ➡️ [[00-语法与鱼皮总览|00-语法与鱼皮总览]]