传播行为定义了当一个事务方法被另一个事务方法调用时,事务应该如何进行。它决定了新方法是加入已有的“事务组”,还是自己单开一个“新项目组”,或者干脆不参与“项目组”(事务)。
核心传播行为详解与示例:
假设我们有两个服务方法:ServiceA.methodA()
和 ServiceB.methodB()
。methodA
内部调用了 methodB
。
1.REQUIRED
(默认) - “一起干”或“开新组”
含义: 如果当前存在事务则加入该事务;如果当前没有事务,则新建一个事务。
场景: 最常用的设置。适用于大多数业务逻辑,保证操作在同一个事务边界内。你在项目组A (methodA
)。现在需要完成一个子任务 (methodB
)。如果项目组A已经存在(有事务),你就直接在组A内完成子任务(加入事务)。如果还没有项目组(无事务),你就自己成立一个新项目组(新建事务)来完成这个子任务(以及后续可能由methodA
做的其他事)。
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
@Transactional(propagation = Propagation.REQUIRED) // 默认就是REQUIRED
public void methodA() {
// 操作A
serviceB.methodB(); // 调用另一个事务方法
// 操作C(如果methodB失败回滚,操作A和操作C也会回滚)
}
}
@Service
public class ServiceB {
@Transactional(propagation = Propagation.REQUIRED) // 加入ServiceA开启的事务
public void methodB() {
// 操作B
}
}
结果: `操作A`, `操作B`, `操作C` 要么一起成功,要么一起失败回滚。它们在一个物理数据库事务中执行。
2. REQUIRES_NEW
- “必须单干”
含义: 总是新建一个事务。如果当前存在事务,则挂起(暂停) 当前事务,先执行新事务。新事务执行完毕(提交或回滚)后,恢复被挂起的事务。
场景: 需要独立于调用者的事务逻辑,无论调用者成功与否,该方法的操作都必须独立提交或回滚。常用于日志记录(即使主业务失败,日志也要记录)、需要强独立性的核心操作。
你是项目组A (methodA
) 的成员。现在有一个非常重要的特殊任务 (methodB
),要求必须单独成立一个项目组来完成,不受主项目组A成功或失败的影响。你暂停了组A的工作(挂起事务),成立新组B(新建事务)去完成任务。完成后(无论B组成功失败),再回到组A继续工作(恢复事务)。
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
@Autowired
private AuditLogService auditLogService; // 假设这是一个记录审计日志的服务
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// 操作A (在事务T1中)
try {
serviceB.criticalOperation(); // REQUIRES_NEW, 开启新事务T2
} catch (Exception e) {
// 即使criticalOperation失败,日志也要记录
}
auditLogService.log("methodA executed"); // REQUIRES_NEW, 开启新事务T3
// 操作C (在事务T1中)
}
}
@Service
public class ServiceB {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void criticalOperation() {
// 操作B (在独立的事务T2中)
}
}
@Service
public class AuditLogService {
@Transactional(propagation = Propagation.REQUIRES_NEW) // 确保日志记录独立提交
public void log(String message) {
// 记录日志到数据库 (在独立的事务T3中)
}
}
结果:`操作A` 和 `操作C` 在事务 T1 中。
`操作B` 在事务 T2 中。 * 审计日志记录在事务 T3 中。
* 即使 `操作B` (`criticalOperation`) 失败导致 T2 回滚,T1(包含操作A、操作C)和 T3(日志)不受影响(除非 `criticalOperation` 抛出的异常被 `methodA` 捕获后导致 T1 也失败)。
* 即使 T1(操作A或操作C失败)回滚,T2(操作B)和 T3(日志)**已经提交成功**(只要它们自己执行期间没出错)。
3. SUPPORTS
- “随大流”
* 含义: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
* 场景: 方法可以适应调用者的事务状态。主要用于查询方法,这些方法在有事务时保证读取一致性,在没有事务时也能执行(但可能读到中间状态)。慎用于修改数据的方法
@Transactional(propagation = Propagation.REQUIRED)
public void updateData() {
// 更新操作1 (在事务中)
String currentValue = readCurrentValue(); // SUPPORTS, 加入事务,读到更新操作1后的值?
// 更新操作2 (在事务中,依赖currentValue)
}
@Transactional(propagation = Propagation.SUPPORTS)
public String readCurrentValue() {
// 读取数据
}
* 如果 `updateData` 在一个事务中调用 `readCurrentValue`,那么 `readCurrentValue` 就在同一个事务中执行,可能读到 `updateData` 中已经修改但未提交的数据(取决于隔离级别)。如果直接调用 `readCurrentValue`(无事务环境),它就以普通查询执行。
4. NOT_SUPPORTED
- “绝不参与” *
含义: 总是以非事务方式执行。如果当前存在事务,则挂起该事务。
* 场景: 方法本身不适合在事务中运行(例如,执行长时间计算、调用不支持事务的外部系统)。它会强制让调用它的方法暂时“脱离”事务环境执行它。
@Transactional(propagation = Propagation.REQUIRED)
public void processOrder() {
// 事务性操作...
externalSystemCall(); // NOT_SUPPORTED, 暂停当前事务,无事务执行
// 恢复事务,继续其他事务操作...
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void externalSystemCall() {
// 调用一个不支持事务或执行非常慢的外部系统接口
}
`externalSystemCall` 的执行不会在数据库事务中进行,即使它失败了,也不会导致 `processOrder` 的事务回滚(除非它抛出的异常没有被处理)。
`processOrder` 的事务在调用 `externalSystemCall` 时被挂起,调用结束后恢复。
5. MANDATORY
- “必须一起干”
含义: 方法必须在一个已存在的事务中运行。如果当前没有事务,则抛出异(IllegalTransactionStateException
)。
*场景: 严格要求方法不能独立运行,必须作为更大事务的一部分。用于强制事务上下文存在。 *
public void businessFlow() {
// 没有开启事务
try {
serviceB.mandatoryOperation(); // 调用MANDATORY方法
} catch (IllegalTransactionStateException e) {
// 因为没有事务,这里会捕获到异常
}
}
@Service
public class ServiceB {
@Transactional(propagation = Propagation.MANDATORY)
public void mandatoryOperation() {
// 必须在一个已存在的事务中运行
}
}
* 如果 `businessFlow` 没有用 `@Transactional` 标注(或者不在其他事务方法内),直接调用 `mandatoryOperation` 会导致异常。
6. NEVER
- “绝不一起干”
含义: 方法绝不能在一个事务中运行。如果当前存在事务,则抛出异常 (IllegalTransactionStateException
)。
场景: 强制要求方法在非事务环境下执行,防止意外加入现有事务导致预期外的行为(如延迟提交、锁持有时间过长)。通常用于纯查询且对实时性要求极高(不希望被前面的事务阻塞)、或操作不支持事务的资源。
@Transactional(propagation = Propagation.REQUIRED)
public void updateAndCheck() {
updateData(); // 更新操作
try {
realtimeStatsCheck(); // 调用NEVER方法
} catch (IllegalTransactionStateException e) {
// 因为updateAndCheck在事务中,这里会捕获到异常
}
}
@Transactional(propagation = Propagation.NEVER)
public void realtimeStatsCheck() {
// 需要实时查询最新统计数据(不希望被updateData的事务阻塞)
}
在事务方法 `updateAndCheck` 中调用 `realtimeStatsCheck` 会抛出异常。