什么是事务传播机制?事务界的"驴友准则"
想象这样一个场景,你想和你的小伙伴一起去旅游,你们准备怎么去:
- 如果你的小伙伴已经有车了(当前存在事务)→ 你是开自己的车还是直接蹭车?
- 如果你的小伙伴没有车(当前不存在事务)→ 你是要买车自己开还是直接放弃?
这就是事务传播机制要解决的问题!它定义了当一个事务方法被另一个事务方法调用时,事务该如何传播。
Spring定义了7种传播行为,表示传播行为的常量定义在TransactionDefinition
中
package org.springframework.transaction;
import org.springframework.lang.Nullable;
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
.......
}
同时为了方便引用,Spring 相应地定义了一个枚举类:Propagation
package org.springframework.transaction.annotation;
public enum Propagation {
REQUIRED(0),
SUPPORTS(1),
MANDATORY(2),
REQUIRES_NEW(3),
NOT_SUPPORTED(4),
NEVER(5),
NESTED(6);
private final int value;
private Propagation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
7种传播机制:事务的"七种人格"
1️⃣ REQUIRED:事务界的"随遇而安者"(默认值)
REQUIRED:默认值,有事务则加入,无事务则新建
类比场景:
"哎哟,已经有人开车了?那我直接蹭车!"
"咦,没有人有车?那我买车开!"
代码示例:
@Transactional
public void createOrder(Order order) {
// 当前没有事务 → 新建事务
inventoryService.reduce(order);
}
@Service
public class InventoryService {
// REQUIRED是默认值,可不写
@Transactional
public void reduce(Order order) {
// 当前已有事务 → 加入已有事务
}
}
适用场景:绝大多数业务方法,保证数据一致性
注意陷阱:
- 一旦加入已有事务,回滚会一起回滚
- 不适合需要独立提交的场景
2️⃣ SUPPORTS:事务界的"佛系青年"
SUPPORTS:有事务则加入,无事务则非事务执行
类比场景:
"有人有车?那我蹭一下"
"没人有车?那大家各自想法解决吧"
代码示例:
@Transactional
public void createOrder(Order order) {
auditService.logAction(); // 加入当前事务
}
@Service
public class AuditService {
@Transactional(propagation = Propagation.SUPPORTS)
public void logAction() {
// 如果有事务 → 加入
// 如果没有事务 → 非事务执行
}
}
适用场景:只读操作,如记录日志、审计跟踪
注意陷阱:
- 不能用于写操作,可能导致数据不一致
- 外部无事务时,内部操作无回滚保障
3️⃣ MANDATORY:事务界的"死硬派"
MANDATORY:必须有事务,否则抛异常
类比场景:
"必须要有车才去!没有?那大家都别去了!"
代码示例:
// 外部调用
public void processWithoutTransaction() {
// 直接调用会报错!
internalService.mustBeInTransaction();
}
@Service
public class InternalService {
@Transactional(propagation = Propagation.MANDATORY)
public void mustBeInTransaction() {
// 必须被事务方法调用
}
}
适用场景:核心业务方法,确保只能在事务环境中执行
注意陷阱:
- 单独调用会直接抛出
IllegalTransactionStateException
- 适合内部服务,不适合对外接口
4️⃣ REQUIRES_NEW:事务界的"独行侠"
REQUIRES_NEW:挂起当前事务,新建事务
类比场景:
"已经有车了?那你们先别开,等我开自己的车到了你们再继续开!"
代码示例:
@Transactional
public void createOrder(Order order) {
try {
// 挂起当前事务,开启新事务
paymentService.processPayment(order);
} catch (Exception e) {
// 即使支付失败,订单仍可提交
}
}
@Service
public class PaymentService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void processPayment(Order order) {
// 独立事务,失败不影响外部事务
}
}
适用场景:
- 需要独立提交的操作,如支付记录、日志记录
- 失败不影响主流程的关键操作
注意陷阱:
- 频繁使用会导致性能下降(多次开启/提交事务)
- 嵌套调用可能导致死锁
5️⃣ NOT_SUPPORTED:事务界的"反社会者"
NOT_SUPPORTED:挂起当前事务,非事务执行
类比场景:
"有车?那你们等等我,我不开车想别的办法过去,我到了你们再开!"
代码示例:
@Transactional
public void createOrder(Order order) {
// 挂起当前事务,非事务执行
externalSystemService.notify(order);
// 继续当前事务
}
@Service
public class ExternalSystemService {
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void notify(Order order) {
// 非事务执行,即使失败也不影响主事务
}
}
适用场景:
- 调用外部系统(如短信、邮件服务)
- 长时间运行的操作,避免占用事务连接
注意陷阱:
- 非事务操作无法回滚
- 可能导致数据最终一致性问题
6️⃣ NEVER:事务界的"洁癖患者"
NEVER:非事务执行,有事务则抛异常
类比场景:
"咱们都是穷人,谁有车就是背叛了组织,就谁也别去了!"
代码示例:
// 外部调用
@Transactional
public void createOrder(Order order) {
// 直接报错!
reportService.generateReport();
}
@Service
public class ReportService {
@Transactional(propagation = Propagation.NEVER)
public void generateReport() {
// 必须在非事务环境下执行
}
}
适用场景:
- 只读报表生成
- 某些特殊数据库操作(如DDL语句)
注意陷阱:
- 被事务方法调用会直接抛出异常
- 使用场景非常有限
7️⃣ NESTED:事务界的"救火队员"
NESTED:嵌套事务,保存点机制
类比场景:
"已经有人开车了?那我设个路标,走错了路我可以自己回到路标处!",如果大家都没车,那我就开自己的车了。
代码示例:
@Transactional
public void createOrder(Order order) {
try {
// 创建保存点,可单独回滚
userService.updateUserPoints(order);
} catch (Exception e) {
// 用户积分更新失败,但不影响订单创建
}
}
@Service
public class UserService {
@Transactional(propagation = Propagation.NESTED)
public void updateUserPoints(Order order) {
// 嵌套事务,可单独回滚
}
}
适用场景:
- 部分操作需要独立回滚
- 复杂业务流程中的子步骤
注意陷阱:
- 依赖数据库的保存点功能(MySQL 5.6+支持)
- 与REQUIRES_NEW的区别:NESTED是同一物理事务
传播机制的"连环套":真实业务场景解析
订单系统的事务链
@Transactional
public void createOrder(Order order) {
// REQUIRED (默认)
orderDao.create(order);
try {
// REQUIRES_NEW - 独立事务,支付记录要保存
paymentService.process(order);
// NESTED - 可单独回滚,不影响主流程
userService.updatePoints(order);
// NOT_SUPPORTED - 调用外部系统,不占用事务
smsService.sendConfirmation(order);
} catch (Exception e) {
// 处理异常
}
// REQUIRED - 加入当前事务
inventoryService.reduce(order);
}
💡 传播机制选择指南
业务需求 | 推荐传播行为 |
---|---|
保证整体一致性 | REQUIRED (默认) |
操作必须在事务中 | MANDATORY |
需要独立提交/回滚 | REQUIRES_NEW |
调用外部系统 | NOT_SUPPORTED |
只读操作 | SUPPORTS |
部分操作可回滚 | NESTED |
绝对不能在事务中 | NEVER |
🔧 事务传播最佳实践
1. 80/20法则:合理控制事务边界
- **80%的方法使用默认的
REQUIRED
- **20%的关键方法显式指定传播行为
2. 事务"瘦身"原则
- 避免在事务中执行耗时操作(如远程调用、复杂计算)
- 使用
NOT_SUPPORTED
处理非关键外部调用
3. 异常与传播的黄金搭配
@Transactional(propagation = Propagation.REQUIRES_NEW,
rollbackFor = Exception.class)
public void criticalOperation() throws Exception {
// 关键操作,任何异常都要回滚
}
4. 事务方法命名规范
- 以
Transactional
结尾:createOrderTransactional
- 或使用注释明确标注:
/* @Transactional */
5. 单元测试验证传播行为
@Test
@Transactional
void testNestedTransaction() {
// 验证NESTED行为
assertThrows(Exception.class, () -> orderService.processWithNested());
// 验证内部操作是否回滚
assertFalse(orderDao.exists(orderId));
}
结语:事务传播的"道"与"术"
事务传播机制就像是需要精准把握的手术刀。用得好,系统健壮如磐石;用不好,数据崩坏如山倒。
下次当你敲下@Transactional(propagation = ...)
时,请先问问自己:
- 这个操作是否需要独立事务?
- 失败时希望如何回滚?
- 与外部系统交互时如何保证一致性?
- 性能影响是否可接受?
最后送大家一张事务传播分类图,收藏起来随时查阅:
事务传播.png