简介
Spring 事务传播机制定义了当一个事务方法被另一个事务方法调用时,事务应该如何传播。它是 Spring 事务管理 (@Transactional) 的核心特性之一,决定了多个事务操作在相互调用时的边界和行为。
Spring 支持以下 7 种传播行为
(定义在 org.springframework.transaction.annotation.Propagation 枚举中):
这里无论是声明性事务还是编程性事务都是这7个级别,都可以设置
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.transaction.annotation;
import org.springframework.transaction.TransactionDefinition;
/**
* Enumeration that represents transaction propagation behaviors for use
* with the {@link Transactional} annotation, corresponding to the
* {@link TransactionDefinition} interface.
*
* @author Colin Sampaleanu
* @author Juergen Hoeller
* @since 1.2
*/
public enum Propagation {
/**
* Support a current transaction, create a new one if none exists.
* Analogous to EJB transaction attribute of the same name.
* <p>This is the default setting of a transaction annotation.
*/
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
/**
* Support a current transaction, execute non-transactionally if none exists.
* Analogous to EJB transaction attribute of the same name.
* <p>Note: For transaction managers with transaction synchronization,
* {@code SUPPORTS} is slightly different from no transaction at all,
* as it defines a transaction scope that synchronization will apply for.
* As a consequence, the same resources (JDBC Connection, Hibernate Session, etc)
* will be shared for the entire specified scope. Note that this depends on
* the actual synchronization configuration of the transaction manager.
* @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization
*/
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
/**
* Support a current transaction, throw an exception if none exists.
* Analogous to EJB transaction attribute of the same name.
*/
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
/**
* Create a new transaction, and suspend the current transaction if one exists.
* Analogous to the EJB transaction attribute of the same name.
* <p><b>NOTE:</b> Actual transaction suspension will not work out-of-the-box
* on all transaction managers. This in particular applies to
* {@link org.springframework.transaction.jta.JtaTransactionManager},
* which requires the {@code jakarta.transaction.TransactionManager} to be
* made available to it (which is server-specific in standard Jakarta EE).
* @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
*/
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
/**
* Execute non-transactionally, suspend the current transaction if one exists.
* Analogous to EJB transaction attribute of the same name.
* <p><b>NOTE:</b> Actual transaction suspension will not work out-of-the-box
* on all transaction managers. This in particular applies to
* {@link org.springframework.transaction.jta.JtaTransactionManager},
* which requires the {@code jakarta.transaction.TransactionManager} to be
* made available to it (which is server-specific in standard Jakarta EE).
* @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
*/
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
/**
* Execute non-transactionally, throw an exception if a transaction exists.
* Analogous to EJB transaction attribute of the same name.
*/
NEVER(TransactionDefinition.PROPAGATION_NEVER),
/**
* Execute within a nested transaction if a current transaction exists,
* behave like {@code REQUIRED} otherwise. There is no analogous feature in EJB.
* <p>Note: Actual creation of a nested transaction will only work on specific
* transaction managers. Out of the box, this only applies to the JDBC
* DataSourceTransactionManager. Some JTA providers might support nested
* transactions as well.
* @see org.springframework.jdbc.datasource.DataSourceTransactionManager
*/
NESTED(TransactionDefinition.PROPAGATION_NESTED);
private final int value;
Propagation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
REQUIRED (默认)
含义: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新事务。
理解: “有则共享,无则自创”。这是最常用、最自然的设置。它保证了多个操作要么都在同一个事务中成功提交,要么一起回滚。
场景: 绝大多数业务逻辑方法。例如,订单服务调用库存服务扣减库存,两者应在同一个事务中。
@Service
public class OrderService {
@Autowired
private InventoryService inventoryService;
@Transactional(propagation = Propagation.REQUIRED) // 默认就是 REQUIRED, 可省略
public void placeOrder(Order order) {
// 1. 保存订单 (当前无事务,创建新事务Tx1)
saveOrder(order);
// 2. 调用库存服务 (当前已有事务Tx1,加入Tx1)
inventoryService.deductStock(order.getProductId(), order.getQuantity());
// ... 其他业务逻辑 ...
} // 整个placeOrder方法成功执行后,事务Tx1提交。如果任何地方抛出RuntimeException,Tx1回滚。
}
@Service
public class InventoryService {
@Transactional(propagation = Propagation.REQUIRED)
public void deductStock(Long productId, int quantity) {
// ... 扣减库存逻辑 ... (在调用者的事务Tx1中执行)
}
}
REQUIRIES_NEW
含义: 无论当前是否存在事务,总是创建一个新的事务。如果当前存在事务,则将当前事务挂起。
理解: “另起炉灶,不受影响”。新事务与调用者的事务完全独立,互不干扰。新事务有自己的提交/回滚。
场景: 需要独立提交、不受外层事务成败影响的子操作。例如:记录审计日志(即使主业务失败,审计日志仍需记录)、调用外部系统(需要确保调用记录)。
@Transactional(propagation = Propagation.REQUIRED)
public void outerMethod() {
// ... 外层业务逻辑 (事务Tx1) ...
try {
auditService.logAction(); // 调用审计服务 (开启新事务Tx2, Tx1被挂起)
} catch (Exception e) {
// 处理审计日志异常,避免影响外层事务
}
// ... 继续外层业务逻辑 (Tx1恢复) ...
// 可能出错导致Tx1回滚
} // 无论Tx1最终提交还是回滚,Tx2(如果成功执行完)已独立提交。
@Service
public class AuditService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logAction() {
// ... 记录审计日志 ... (在独立的事务Tx2中执行)
} // Tx2 在此提交(如果没有异常)
}
SUPPORTS
含义: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式继续执行。
理解: “随遇而安”。方法本身不强制要求事务,但有就用,没有就算了。
场景: 查询方法、日志记录等非核心业务逻辑,可以容忍数据在事务内外不一致的短暂情况(如只读查询)。
@Service
public class BusinessService {
@Autowired
private LogService logService; // 注入日志服务Bean
@Transactional(propagation = Propagation.REQUIRED)
public void businessMethod() {
// ... 核心业务逻辑 (在事务Tx1中) ...
logService.logOperation(); // 调用另一个Bean的方法,会通过代理
}
}
@Service
public class LogService {
@Transactional(propagation = Propagation.SUPPORTS)
public void logOperation() {
// 现在这个调用会经过Spring代理,传播行为SUPPORTS生效
// 如果被businessMethod (有事务) 调用,则加入Tx1
// 如果被无事务方法调用,则无事务执行
}
}
NOT_SUPPORTED
含义: 以非事务方式执行操作。如果当前存在事务,则将当前事务挂起。
理解: “拒绝加入,暂停你们”。该方法明确要求不在事务中运行,并暂停调用者的任何现有事务。
场景: 需要在事务外执行的操作,比如某些性能敏感且不需要事务保证的批量处理、调用不支持事务的资源(如某些 NoSQL)。慎用,因为它会破坏事务边界。
@Service
@RequiredArgsConstructor
public class DataExportService {
private final ExportProcessor exportProcessor;
private final StatsService statsService;
@Transactional
public void exportTransactionalData() {
// 事务操作(Tx1)
List<Data> data = dataRepository.findRecent();
// 挂起事务执行导出
exportProcessor.exportLargeData(data); // 挂起 Tx1
// 更新统计(在恢复的事务 Tx1 中)
statsService.updateExportStats();
}
}
@Service
public class ExportProcessor {
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void exportLargeData(List<Data> data) {
// 在无事务上下文中执行(Tx1 被挂起)
// 长时间操作,不需要事务保证
data.forEach(item -> externalSystem.send(item));
}
}
MANDATORY
含义: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常(IllegalTransactionStateException)。
理解: “必须在事务中运行”。该方法强制要求在一个已存在的事务上下文中被调用。
场景: 某些必须作为更大事务一部分执行的关键操作。确保调用者已经开启了事务。
@Service
@RequiredArgsConstructor
public class AuditService {
private final AuditLogService auditLogService;
@Transactional // 确保在事务中调用
public void auditAction(String action) {
// 必须在事务中记录审计日志
auditLogService.logAction(action);
}
}
@Service
public class AuditLogService {
@Transactional(propagation = Propagation.MANDATORY)
public void logAction(String action) {
// 如果被非事务方法调用,将抛出 IllegalTransactionStateException
auditLogRepository.save(new AuditLog(action));
}
}
NEVER
含义: 以非事务方式执行。如果当前存在事务,则抛出异常(IllegalTransactionStateException)。
理解: “禁止在事务中运行”。该方法要求绝对不能在事务上下文中被调用。
场景: 某些明确不能有事务参与的操作(虽然少见)。确保方法执行时没有事务干扰。
// ===================== 6. NEVER =====================
@Service
@RequiredArgsConstructor
public class CacheWarmupService {
private final CacheLoader cacheLoader;
// 非事务方法
public void warmupCaches() {
// 必须在非事务环境中执行
cacheLoader.preloadUserCache();
}
}
@Service
public class CacheLoader {
@Transactional(propagation = Propagation.NEVER)
public void preloadUserCache() {
// 如果被事务方法调用,将抛出 IllegalTransactionStateException
List<User> users = userRepository.findAll();
cacheManager.load("users", users);
}
}
NESTED
含义: 如果当前存在事务,则在当前事务的一个嵌套事务中执行。嵌套事务是外层事务的一部分,但可以有自己的保存点。如果当前没有事务,其行为与 REQUIRED 一样(创建一个新事务)。
理解: “子事务”。嵌套事务的提交依赖于外层事务的提交。只有外层事务提交时,嵌套事务的修改才会最终提交。嵌套事务可以独立回滚到其保存点,而不影响外层事务(除非回滚到保存点之前);但外层事务回滚会导致整个嵌套事务回滚。
关键点:
依赖于底层数据库或 JDBC 驱动支持保存点(大多数主流数据库都支持)。
不是 JTA 标准的一部分,主要是特定于某些资源(如 JDBC)的概念。
与 REQUIRES_NEW 的区别: REQUIRES_NEW 创建的是完全独立的、物理上的新事务。NESTED 创建的是逻辑上的子事务(利用保存点实现),是外层事务的一部分。
// ===================== 7. NESTED =====================
@Service
@RequiredArgsConstructor
public class BatchProcessor {
private final ItemProcessor itemProcessor;
@Transactional
public void processBatch(List<Item> items) {
batchRepository.startBatch();
for (Item item : items) {
try {
// 每个项目在嵌套事务中处理
itemProcessor.processItem(item);
} catch (ItemProcessingException e) {
// 单个项目失败不影响整体批次
errorRepository.logError(item, e);
}
}
batchRepository.completeBatch();
}
}
@Service
public class ItemProcessor {
@Transactional(propagation = Propagation.NESTED)
public void processItem(Item item) {
// 嵌套事务(使用保存点)
// 失败时只回滚此操作,不影响外层事务
itemRepository.process(item);
inventoryService.adjustStock(item);
}
}
总结
REQUIRED (默认): 最常用,保证方法在事务中运行(加入或新建)。
REQUIRES_NEW: 需要绝对独立提交/回滚时使用(如审计日志)。会挂起当前事务。
SUPPORTS/NOT_SUPPORTED: 灵活适应调用环境(有就用/拒绝用)。
MANDATORY/NEVER: 强制约束调用环境(必须/禁止事务)。
NESTED: 需要子操作部分失败不影响整体时使用(利用保存点)。是外层事务的一部分。需要数据库支持保存点。
事务的自调用问题
事务的自调用会让事务失效
可以参考这篇文章spring事务的自调用问题
上面这篇文章 只举了一个例子
声明性事务自调用问题
简单来说就是自调用的事务你不管用的什么传播级别,都会失效
public void outer() {
this.innerNotSupported(); // 自调用,NOT_SUPPORTED 失效
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void innerNotSupported() {
// 预期:挂起当前事务(如果有)
// 实际:注解失效,方法在调用者的事务上下文中执行
}
如果outer也有事务
那么innerNotSupported这事务标注的方法就会失效,加入到outer里面的事务
@Transactional(propagation = Propagation.REQUIRED )
public void outer() {
this.innerNotSupported(); // 自调用,NOT_SUPPORTED 失效
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void innerNotSupported() {
// 预期:挂起当前事务(如果有)
// 实际:注解失效,方法在调用者的事务上下文中执行
}
编程性事务没有自调用问题
public void outerMethod() {
// 编程式事务无自调用问题
transactionTemplate.execute(status -> {
innerNeverMethod(); // 直接调用
return null;
});
}
private void innerNeverMethod() {
// 因为事务边界由TransactionTemplate明确控制
// 不需要@Transactional注解也可实现事务控制
}
总结
多个事务联合调用的时候,自调用主要的特点:
1.主事务不受影响
@Transactional 修饰的外部入口方法的事务仍然有效
2.内部方法事务特性完全丢失
传播行为/隔离级别/超时设置等全部失效
3.但仍在主事务中执行
这是最隐蔽的危险:方法仍在主事务上下文运行,但失去了事务配置的保护
解决方案:
这里其实最常见的就是服务拆分,使用代理