第一部分:事务的本质——从 ACID 到 Spring 抽象

在聊 Spring 事务之前,我们必须回归本质。

1. 事务的 ACID 原则

  • 原子性 (Atomicity):逻辑上一组操作,要么全成功,要么全失败。

  • 一致性 (Consistency):执行事务前后,数据保持一致。

  • 隔离性 (Isolation):并发访问时,事务之间不互相干扰。

  • 持久性 (Durability):一旦提交,改变是永久性的。

2. Spring 事务管理的核心三剑客

Spring 并没有直接管理事务,而是通过一个抽象层来统一不同的数据源(JDBC, Hibernate, MyBatis)。

  • PlatformTransactionManager:核心接口,负责定义事务的获取、提交、回滚。

  • TransactionDefinition:定义事务规则(隔离级别、传播行为、超时等)。

  • TransactionStatus:代表当前事务的状态。


第二部分:编程式事务 vs 声明式事务

面试官:“如果你需要更精细地控制事务范围,你会怎么做?”

1. 声明式事务 (@Transactional)

  • 优势:非侵入式,代码简洁。

  • 代价:基于 AOP 实现,容易因为各种“姿势不对”导致事务失效。

2. 编程式事务 (TransactionTemplate)

作为 10 年老兵,我强烈建议在大事务场景下使用编程式事务,将非数据库操作(如 RPC 调用、文件读写)排除在事务外,防止长时间占用连接。

Java

@Autowired
private TransactionTemplate transactionTemplate;
​
public void saveTrade() {
    // 非事务性操作:参数校验、RPC查询
    checkData();
    
    transactionTemplate.execute(status -> {
        try {
            // 真正的数据库事务操作
            orderMapper.insert(order);
            stockMapper.update(stock);
            return true;
        } catch (Exception e) {
            status.setRollbackOnly(); // 手动回滚
            return false;
        }
    });
}

第三部分:深度拆解——事务的七大传播行为

这是大厂面试的“重灾区”,你需要背得滚瓜烂熟。

传播行为 描述 核心应用场景
REQUIRED 如果当前有事务,加入;没有则新建(默认)。 绝大多数增删改逻辑。
REQUIRES_NEW 挂起当前事务,新建独立事务。 无论主业务是否成功,都要记录的操作(如审计日志)。
NESTED 如果有事务,则在嵌套事务内执行;没有则新建。 部分失败可重试的子业务,且依赖主事务提交。
SUPPORTS 有事务就用,没有就以非事务方式执行。 查询逻辑。
MANDATORY 强制要求有事务,否则报错。 严谨的内部核算逻辑。
NOT_SUPPORTED 以非事务方式执行,有事务则挂起。 耗时长的、不需要保证一致性的操作。
NEVER 强制以非事务方式执行,有事务则报错。 严格禁止开启事务的特殊逻辑。

第四部分:事务失效的“十大酷刑”——为什么回滚失败?

作为面试官,我会给你一段代码,让你找 Bug。常见的失效原因如下:

1. 访问权限问题

@Transactional 只能用于 public 方法。这是由 Spring AOP 的代理机制决定的。

2. 方法自调用(Self-Injection)

致命伤:在同一个类中,A 方法调用 B 方法,即使 B 方法加了事务注解,也会失效。

  • 原因:由于是 this 调用,没走代理对象。

  • 对策:使用 AopContext.currentProxy() 或者自己注入自己。

3. 异常类型不匹配

默认情况下,Spring 只对 RuntimeExceptionError 进行回滚。

  • 对策:显式指定 @Transactional(rollbackFor = Exception.class)

4. 数据库引擎不支持

如果你的 MySQL 表使用的是 MyISAM 而不是 InnoDB,事务注解只是个摆设。


第五部分:面试复盘脑图

为了方便你总结,我为你准备了这张事务架构思维导图:

Code snippet

mindmap
  root((Spring 事务体系))
    管理方式
      编程式: TransactionTemplate, 灵活, 适合大事务
      声明式: @Transactional, AOP实现, 开发效率高
    核心参数
      隔离级别: Read Uncommitted -> Serializable
      传播行为: REQUIRED, REQUIRES_NEW, NESTED等
      超时与只读: timeout, readOnly
      回滚策略: rollbackFor 默认为 RuntimeException
    失效大坑
      方法自调用: 未经代理对象, this陷阱
      非Public方法: 代理增强失败
      异常被Catch: 代理感知不到异常
      非Spring管理: 类未加@Service/@Component
    底层原理
      Proxy代理: JDK/CGLIB
      TransactionInterceptor: 环绕通知
      ThreadLocal: 保证同一线程内Connection一致

第六部分:大厂面试官的“夺命连环炮”

  1. Spring 事务如何保证多个 DAO 操作使用同一个连接?

    • 回答要点:通过 TransactionSynchronizationManager 结合 ThreadLocal。在事务开始时将 Connection 绑定到当前线程,后续 DAO 操作直接从 ThreadLocal 获取。

  2. REQUIRES_NEW 和 NESTED 有什么区别?

    • 回答要点REQUIRES_NEW 是两个独立的事务,子事务提交与否不影响父事务(除非子事务抛异常被父感知)。而 NESTED 是嵌套事务,底层使用数据库的 Savepoint(保存点)。父事务回滚,子事务一定回滚;子事务回滚,父事务可以选择捕获异常后不回滚。

  3. 大事务(Long-running Transaction)有什么危害?

    • 回答要点

      1. 锁定数据库连接,导致连接池枯竭。

      2. 锁定数据行,导致并发性能下降(死锁风险)。

      3. 产生巨量 Undo Log,导致磁盘空间压力。


结语:从“背题”到“设计”

事务不仅仅是代码问题,更是对数据库底层锁机制与并发模型的深度考量。

这篇文章总结了 Spring 事务最核心的命门。如果你在面试中能结合具体项目案例(如:我在重构支付系统时,如何将大事务拆解为小事务+本地消息表),那么 Offer 基本稳了。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐