导语:事务传播机制是Spring事务中最容易踩坑的知识点之一!本文通过真实代码演示+日志分析,带你彻底搞懂7种传播行为,从此不再被面试官问倒!
面试官(优化版):事务传播机制到底怎么选?
通俗理解:
事务传播机制就像“多个任务协作时的分工规则”——当方法A调用方法B时,B的事务该如何处理?是跟着A干,还是自己单干?
下面由"弱到强"详细讲解Spring的7种传播规则(MANDATORY抛异常->SUPORT不处理->REQUIRDE新建->REQUIRES_NEW总是新建->NOT_SUPPORTED挂起->NEVER抛异常;DESTED嵌套)。
首先是准备两个方法:
// UserService
public void addUser() {
userMapper.insert(user); // 插入用户
}
// BlogService
public void addBlog() {
blogMapper.insert(blog); // 插入博客
}
1.MANDATORY
- 规则:必须跟着大哥干,没大哥就罢工(抛异常);如果当前存在事务,则加入该事务;如果当前没有事务,它会抛出异常。这种传播行为要求调用方法必须在一个现有的事务中运行
- 场景:强制要求方法在事务中被调用(如敏感操作审计)
- 代码验证:
// 直接调用会抛异常!
@Transactional(propagation = Propagation.MANDATORY)
public void addBlog() {
blogMapper.insert(blog);
}
// 没有事务的方法调用也会抛异常
public void addUser() {
userMapper.insert(user); // 插入用户
blogService.addBlog(); // 调用博客服务
}
- 异常日志:
No existing transaction found for transaction marked with propagation 'mandatory'
2. SUPPORTS
- 规则:随遇而安,有事务就跟,没有也不强求;如果当前存在事务,方法会在这个事务中运行;如果没有事务,则方法在非事务的环境中执行。
- 场景:查询方法适配(如根据业务决定是否走事务)
- 踩坑点:
@Transactional(propagation = Propagation.SUPPORTS)
public void addBlog() {
blogMapper.insert(blog); // 非事务环境插入数据
throw new RuntimeException(); // 异常不会回滚!
}
- 现象:数据成功插入,不回滚(因为没有事务环境)
3. REQUIRED(默认)
- 规则:有活一起干,没活自己干;默认的事务传播级别。如果当前没有事务,Spring 会创建一个新的事务;如果已经存在事务,则加入该事务。这意味着被调用的方法会在调用者的事务上下文中运行。
- 场景:90%的通用场景(如订单创建+库存扣减)
- 代码验证:
// UserService
@Transactional(propagation = Propagation.REQUIRED)
public void addUser() {
userMapper.insert(user); // 插入用户
blogService.addBlog(); // 调用博客服务
}
// BlogService
@Transactional(propagation = Propagation.REQUIRED)
public void addBlog() {
blogMapper.insert(blog); // 插入博客
int i = 1/0; // 抛出异常
}
-
日志分析:
Creating new transaction
(addUser开启事务)Participating in existing transaction
(addBlog加入事务)- 最终全部回滚(同一事务内异常导致整体失败)
4. REQUIRES_NEW
- 规则:必须单干,暂停大哥的活;总会创建一个新的事务,如果当前存在事务,则将当前事务挂起。也就是说不管外部方法是否开启事务,
REQUIRES_NEW
修饰的方法都会新开启自己的事务,且开启的事务相互独立,互不干扰。 - 场景:独立业务(如日志记录必须成功)
- 代码演示:
// UserService
@Transactional(propagation = Propagation.REQUIRED)
public void addUser() {
userMapper.insert(user);
try {
blogService.addBlog(); // 调用REQUIRES_NEW方法
} catch (Exception e) {
// 即使这里捕获异常,用户数据仍会提交!
}
}
// BlogService
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addBlog() {
blogMapper.insert(blog);
throw new RuntimeException(); // 触发回滚
}
-
日志关键点:
Suspending current transaction
(挂起主事务)Creating new transaction
(开启新事务)- 结果:用户数据提交成功,博客数据回滚
5. NOT_SUPPORTED
- 规则:拒绝事务,有也暂停;以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- 场景:非核心操作(如批量数据导入)
- 代码效果:
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void addBlog() {
blogMapper.insert(blog); // 非事务执行
throw new RuntimeException(); // 数据仍插入成功!
}
6. NEVER
- 规则:禁止在事务中调用,否则翻脸;以非事务方式运行,如果当前存在事务,它会抛出异常。这种传播行为确保方法永远不在事务环境中运行。
- 场景:性能敏感的非事务操作
- 异常演示:
@Transactional
public void addUser() {
blogService.addBlog(); // 调用NEVER方法
}
// BlogService
@Transactional(propagation = Propagation.NEVER)
public void addBlog() { /*...*/ }
- 结果:直接抛出
IllegalTransactionStateException
7. NESTED
- 规则:父子事务,子可单独回滚;如果当前存在事务,则在嵌套事务内运行。如果当前事务回滚,嵌套事务也会回滚,但嵌套事务的回滚不会导致当前事务回滚。如果当前没有事务,则其行为像REQUIRED。
- 场景:复杂业务分层(如订单拆分子订单)
- 代码演示:
// UserService(父事务)
@Transactional
public void addUser() {
userMapper.insert(user);
try {
blogService.addBlog(); // 嵌套事务
} catch (Exception e) {
// 子事务回滚,父事务继续
}
}
// BlogService
@Transactional(propagation = Propagation.NESTED)
public void addBlog() {
blogMapper.insert(blog);
throw new RuntimeException();
}
- 结果:博客数据回滚,用户数据保留
避坑指南
-
事务日志调试技巧:
在logback.xml
添加配置,实时观察事务行为:<logger name="org.springframework.transaction" level="DEBUG"/>
运行 HTML
-
异常处理黄金法则:
- 受检异常(Exception)默认不回滚
- 运行时异常(RuntimeException)默认回滚
- 务必用
rollbackFor
指定需要回滚的异常类型!
-
自调用失效问题:
// 错误!同类调用不经过代理 public void methodA() { this.methodB(); } // 正确:通过代理对象调用 @Autowired private UserService selfProxy; // 注入自身代理
总结
事务传播行为选择口诀:
- 默认就用REQUIRED
- 独立操作REQUIRES_NEW
- 强制校验MANDATORY
- 非事务选NOT_SUPPORTED
- 多层嵌套用NESTED
下期预告:《Spring事务失效的10大魔鬼场景!你中过几个?》
讨论:你在使用传播机制时遇到过哪些“坑”?欢迎留言分享!