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 → 回滚
}

三个默认行为先记住:

  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 才支持(详见 容器块 外的 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 不设置时就是数据库默认值。

勾连


⬅️ 04-Bean生命周期与三级缓存 🏠 00-Java ➡️ 00-语法与鱼皮总览