Spring 事务全链路深度解析与大厂面试避坑指南
第一部分:事务的本质——从 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 只对 RuntimeException 和 Error 进行回滚。
-
对策:显式指定
@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一致
第六部分:大厂面试官的“夺命连环炮”
-
Spring 事务如何保证多个 DAO 操作使用同一个连接?
-
回答要点:通过
TransactionSynchronizationManager结合 ThreadLocal。在事务开始时将 Connection 绑定到当前线程,后续 DAO 操作直接从 ThreadLocal 获取。
-
-
REQUIRES_NEW 和 NESTED 有什么区别?
-
回答要点:
REQUIRES_NEW是两个独立的事务,子事务提交与否不影响父事务(除非子事务抛异常被父感知)。而NESTED是嵌套事务,底层使用数据库的 Savepoint(保存点)。父事务回滚,子事务一定回滚;子事务回滚,父事务可以选择捕获异常后不回滚。
-
-
大事务(Long-running Transaction)有什么危害?
-
回答要点:
-
锁定数据库连接,导致连接池枯竭。
-
锁定数据行,导致并发性能下降(死锁风险)。
-
产生巨量 Undo Log,导致磁盘空间压力。
-
-
结语:从“背题”到“设计”
事务不仅仅是代码问题,更是对数据库底层锁机制与并发模型的深度考量。
这篇文章总结了 Spring 事务最核心的命门。如果你在面试中能结合具体项目案例(如:我在重构支付系统时,如何将大事务拆解为小事务+本地消息表),那么 Offer 基本稳了。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)