一、核心概念与七种行为汇总
事务传播行为 定义了一个事务方法在调用另一个事务方法时,事务该如何传递和交互。它解决的核心问题是:当业务方法嵌套调用时,事务的边界应如何界定?
Spring 定义了 7 种传播行为:
| 传播行为 | 常量值 | 核心行为描述(当方法被调用时) | 典型应用场景 |
|---|---|---|---|
| REQUIRED (默认) | Propagation.REQUIRED |
"需要有事务":如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。 | 绝大多数业务方法,确保操作在同一个事务中。 |
| SUPPORTS | Propagation.SUPPORTS |
"支持事务":如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续执行。 | 查询方法,可以接受在事务内或事务外执行。 |
| MANDATORY | Propagation.MANDATORY |
"强制要有事务":如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。 | 严格要求必须在已有事务中被调用的方法。 |
| REQUIRES_NEW | Propagation.REQUIRES_NEW |
"必须有新事务":无论如何,都会创建一个新的事务。如果当前存在事务,则将其挂起。 | 需要独立提交、不受外部事务影响的子操作(如审计日志)。 |
| NOT_SUPPORTED | Propagation.NOT_SUPPORTED |
"不支持事务":以非事务方式执行。如果当前存在事务,则将其挂起。 | 需要在无事务环境下执行,例如调用不支持事务的遗留接口。 |
| NEVER | Propagation.NEVER |
"绝不要事务":以非事务方式执行。如果当前存在事务,则抛出异常。 | 严格要求不能在事务中被调用的方法。 |
| NESTED | Propagation.NESTED |
"嵌套事务" :如果当前存在事务,则在嵌套事务(基于保存点)内执行;如果当前没有事务,其行为同REQUIRED。 |
需要支持部分回滚的复杂业务,如批量处理。 |
二、传播行为核心要解决的问题
想象一个典型场景:
java
@Service
public class OrderService {
@Transactional
public void placeOrder(Order order) {
// 1. 保存订单主信息
orderDao.save(order);
// 2. 更新库存(调用另一个事务方法)
inventoryService.deductStock(order.getItems());
// 3. 记录操作日志
logService.addLog(order);
}
}
当 placeOrder() 已开启事务,而它调用的 deductStock() 也标注了 @Transactional 时,两个事务是什么关系?
- 合并成一个事务(同生共死)?
- 各自独立(库存失败,订单依然提交)?
- 内部方法不启事务(沿用外部事务)?
传播行为(Propagation) 就是用来回答这个问题的。
三、7种传播行为详解
Spring 定义了 7 种传播行为,按使用频率和重要性可分为 核心常用 和 特殊场景 两类。
3.1 核心常用的传播行为
| 传播行为 | 常量值 | 核心语义 | 适用场景 |
|---|---|---|---|
| REQUIRED (默认) | Propagation.REQUIRED |
"需要有事务":有则加入,无则新建 | 90%的场景,标准的原子性操作 |
| REQUIRES_NEW | Propagation.REQUIRES_NEW |
"必须有新事务":无论有无,都创建新事务,原事务挂起 | 需要独立提交/回滚的操作(如审计日志) |
| NESTED | Propagation.NESTED |
"嵌套事务":有则创建保存点,无则新建(类似REQUIRED) | 支持部分回滚的复杂业务 |
| SUPPORTS | Propagation.SUPPORTS |
"支持事务":有则加入,无则以非事务执行 | 查询方法,不改变数据的事务可有可无 |
1. REQUIRED(默认且最常用)
java
@Service
public class UserService {
@Transactional(propagation = Propagation.REQUIRED) // 可省略,默认就是它
public void createUser(User user) {
userDao.save(user);
profileService.initProfile(user.getId()); // 也是REQUIRED
}
}
@Service
public class ProfileService {
@Transactional(propagation = Propagation.REQUIRED)
public void initProfile(Long userId) {
// 此时会加入外部 createUser 的事务
// 两者同在一个事务中,一荣俱荣,一损俱损
}
}
特点 :形成统一的事务边界,任何方法失败都会导致全部回滚。
2. REQUIRES_NEW(强隔离)
java
@Service
public class OrderService {
@Transactional(propagation = Propagation.REQUIRED)
public void processOrder(Order order) {
orderDao.save(order); // 在事务A中
// 新启事务B,事务A被挂起
auditService.logOperation("CREATE_ORDER", order.getId());
// 事务A恢复,继续执行
inventoryService.update(order); // 仍在事务A中
}
}
@Service
public class AuditService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logOperation(String action, Long targetId) {
// 总是在自己的新事务中执行
// 即使外部事务回滚,这条日志也一定会提交
}
}
特点:
- 创建完全独立的新事务
- 原事务被挂起,新事务提交/回滚后,原事务恢复
- 新事务的回滚不会影响原事务(除非原事务捕获了异常)
- 原事务的回滚不会影响已提交的新事务
3. NESTED(嵌套事务 - 数据库需支持保存点)
java
@Service
public class BatchService {
@Transactional
public void batchImport(List<Product> products) {
for (int i = 0; i < products.size(); i++) {
try {
productService.importProduct(products.get(i)); // NESTED传播
} catch (DataIntegrityViolationException e) {
// 仅跳过当前产品,继续导入下一个
log.error("产品{}导入失败: {}", i, e.getMessage());
}
}
}
}
@Service
public class ProductService {
@Transactional(propagation = Propagation.NESTED)
public void importProduct(Product product) {
// 如果当前有事务,会设置保存点
// 此方法失败只会回滚到保存点,不会影响外部事务
}
}
特点:
- 外部事务内创建保存点(Savepoint)
- 内部方法失败时,只回滚到保存点,外部事务可选择继续
- 外部事务提交时,所有嵌套操作一起提交
- 外部事务回滚时,所有嵌套操作一起回滚
- 数据库必须支持保存点(如MySQL的InnoDB支持)
4. SUPPORTS(查询优化)
java
@Service
public class QueryService {
@Transactional(propagation = Propagation.SUPPORTS) // 有事务就用,没有就算了
public User getUser(Long id) {
// 如果在事务中,则享受事务的只读/隔离级别好处
// 如果无事务,直接执行查询
return userDao.findById(id);
}
}
3.2 特殊场景的传播行为
| 传播行为 | 常量值 | 核心语义 | 注意点 |
|---|---|---|---|
| MANDATORY | Propagation.MANDATORY |
"强制要有事务":有则加入,无则抛异常 | 严格的事务环境要求 |
| NOT_SUPPORTED | Propagation.NOT_SUPPORTED |
"不支持事务":以非事务执行,原事务挂起 | 强制非事务执行,用于不兼容事务的操作 |
| NEVER | Propagation.NEVER |
"绝不要事务":有事务则抛异常 | 严格非事务环境要求 |
使用示例
java
// MANDATORY:必须被一个事务方法调用
@Transactional(propagation = Propagation.MANDATORY)
public void updateBalance(Long userId, BigDecimal amount) {
// 此方法绝不能直接被非事务方法调用
}
// NOT_SUPPORTED:强制非事务(如调用外部不支持事务的遗留系统)
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void syncToLegacySystem(Data data) {
// 即使调用方有事务,这里也会挂起事务
legacyClient.send(data); // 不支持事务的老系统
}
// NEVER:严格禁止事务
@Transactional(propagation = Propagation.NEVER)
public void clearCache() {
// 纯内存操作,不需要事务
// 如果调用方有事务,直接抛异常
}
3.3. 核心对比:REQUIRED vs REQUIRES_NEW vs NESTED
| 场景 | 外部有事务时 | 外部无事务时 | 回滚影响范围 | 适用场景 |
|---|---|---|---|---|
| REQUIRED | 加入外部事务 | 新建事务 | 全部回滚 | 标准业务操作(默认选择) |
| REQUIRES_NEW | 挂起外部事务,建新事务 | 新建事务 | 各自独立 | 审计日志、监控数据 |
| NESTED | 嵌套事务(保存点) | 新建事务(同REQUIRED) | 部分回滚(到保存点) | 批量操作(允许部分失败) |
四、实际应用中的选择策略
4.1 决策流程
是否需要强隔离(失败不影响调用方)?
├─ 是 → REQUIRES_NEW(如:操作日志)
│
├─ 否 → 是否需要部分回滚能力?
│ ├─ 是 → NESTED(如:批量导入)
│ │
│ └─ 否 → REQUIRED(默认,90%场景)
│
└─ 特殊情况:
├─ 查询方法 → SUPPORTS
├─ 强制事务环境 → MANDATORY
├─ 强制非事务 → NOT_SUPPORTED/NEVER
└─ 避免使用 → SUPPORTS/NOT_SUPPORTED/NEVER(除非明确需求)
4.2 经典场景配置
java
@Service
@Transactional // 类级别默认REQUIRED
public class OrderServiceImpl implements OrderService {
// 1. 核心业务:使用默认REQUIRED
public Order createOrder(OrderRequest request) {
// 订单、库存、支付在同一事务
}
// 2. 需要独立提交的审计日志
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void auditLog(String action, String detail) {
// 即使订单失败,日志也要保留
}
// 3. 批量处理:允许单条失败
@Transactional
public BatchResult batchProcess(List<Item> items) {
for (Item item : items) {
try {
processItem(item); // 内部NESTED事务
} catch (BusinessException e) {
// 记录失败,继续处理下一个
failedItems.add(item);
}
}
return new BatchResult(failedItems);
}
@Transactional(propagation = Propagation.NESTED)
private void processItem(Item item) {
// 单条处理,失败只影响自己
}
// 4. 纯查询:不需要严格事务
@Transactional(propagation = Propagation.SUPPORTS,
readOnly = true)
public Order queryOrder(Long id) {
return orderDao.findById(id);
}
}
五、重要注意事项
5.1 基于代理的局限
Spring事务基于AOP代理实现,自调用会失效:
java
@Service
public class ProblemService {
@Transactional
public void methodA() {
methodB(); // ❌ 自调用,事务注解不生效!
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// 不会创建新事务,仍使用methodA的事务
}
}
解决方案:
- 注入自身代理(不推荐)
- 将methodB提取到另一个Service中(推荐)
5.2 异常回滚规则
默认只回滚RuntimeException 和Error,受检异常(Checked Exception)不会触发回滚。
java
@Transactional(propagation = Propagation.REQUIRED,
rollbackFor = {IOException.class, BusinessException.class})
public void process() throws IOException {
// 现在IOException也会触发回滚
}
5.3 性能影响
- REQUIRES_NEW:开销最大,涉及事务挂起/恢复
- NESTED:保存点有额外开销,但小于新建事务
- REQUIRED:性能最优
六、最佳实践总结
- 无明确需求时,保持默认 :绝大多数方法使用
REQUIRED即可。 - 谨慎使用 REQUIRES_NEW:虽然隔离性好,但会带来性能开销和复杂的事务管理,仅在必要时使用。
- 明确使用只读事务 :对于纯查询方法,结合
@Transactional(readOnly = true)使用SUPPORTS或REQUIRED,能带来性能优化。 - 处理好异常 :默认情况下,Spring 事务只在遇到
RuntimeException和Error时回滚。受检异常(Checked Exception)不会触发回滚,如有需要请使用rollbackFor参数显式配置。 - 保持事务精简:事务中应只包含必要的数据库操作,避免长时间持有数据库连接。