副标题:7种传播行为,搞懂事务的前世今生!🎯
🎬 开场:事务传播是什么?
问题场景
场景:方法A调用方法B
methodA() {
@Transactional
// 执行数据库操作1
methodB(); // 调用methodB
// 执行数据库操作2
}
methodB() {
@Transactional
// 执行数据库操作3
}
问题:
methodB应该加入methodA的事务?
还是开启新的事务?
还是不使用事务?
这就是事务传播(Transaction Propagation)要解决的问题!
真实案例
某电商系统的订单创建:
createOrder() { // 创建订单(事务A)
@Transactional
1. 创建订单记录
2. 扣减库存 → 调用 deductStock()
3. 扣减余额 → 调用 deductBalance()
4. 发送通知 → 调用 sendNotification()
}
deductStock() { // 扣减库存(事务B)
@Transactional
UPDATE products SET stock = stock - 1
}
问题:
如果deductStock()失败,
整个订单创建应该回滚吗?
答案取决于事务传播行为!
📚 7种传播行为
传播行为列表
Spring事务传播行为(7种):
1. REQUIRED(默认)⭐⭐⭐⭐⭐
- 有事务就加入,没有就新建
2. SUPPORTS
- 有事务就加入,没有就非事务执行
3. MANDATORY
- 必须有事务,否则抛异常
4. REQUIRES_NEW ⭐⭐⭐⭐
- 总是新建事务,挂起当前事务
5. NOT_SUPPORTED
- 总是非事务执行,挂起当前事务
6. NEVER
- 必须非事务执行,有事务就抛异常
7. NESTED ⭐⭐⭐
- 嵌套事务,使用SavePoint
🎯 详解每种传播行为
1️⃣ REQUIRED(默认)⭐⭐⭐⭐⭐
最常用的传播行为!
/**
* REQUIRED:有事务就加入,没有就新建
*/
@Service
public class OrderService {
@Autowired
private StockService stockService;
/**
* 创建订单(事务A)
*/
@Transactional(propagation = Propagation.REQUIRED) // 默认值
public void createOrder() {
System.out.println("1. 创建订单");
// 调用扣减库存(事务B)
stockService.deductStock();
System.out.println("3. 订单创建完成");
}
}
@Service
public class StockService {
/**
* 扣减库存(事务B)
*/
@Transactional(propagation = Propagation.REQUIRED) // 默认值
public void deductStock() {
System.out.println("2. 扣减库存");
// 如果这里抛异常,整个事务回滚
if (stock <= 0) {
throw new RuntimeException("库存不足");
}
}
}
执行流程:
情况1:外部有事务
createOrder() { // 开启事务A
@Transactional(REQUIRED)
创建订单 ← 在事务A中
deductStock() { // 加入事务A
@Transactional(REQUIRED)
扣减库存 ← 在事务A中
}
订单创建完成 ← 在事务A中
} // 提交事务A
结果:
- deductStock()加入createOrder()的事务
- 两个方法在同一个事务中
- 任何一个失败,整个事务回滚
情况2:外部无事务
deductStock() { // 开启事务B
@Transactional(REQUIRED)
扣减库存 ← 在事务B中
} // 提交事务B
结果:
- deductStock()新建自己的事务
2️⃣ REQUIRES_NEW ⭐⭐⭐⭐
总是新建事务,挂起当前事务!
/**
* REQUIRES_NEW:总是新建事务
*/
@Service
public class OrderService {
@Autowired
private LogService logService;
/**
* 创建订单(事务A)
*/
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder() {
System.out.println("1. 创建订单");
try {
// 记录日志(新事务B)
logService.saveLog("创建订单");
} catch (Exception e) {
// 日志失败不影响订单创建
System.out.println("日志记录失败");
}
System.out.println("3. 订单创建完成");
}
}
@Service
public class LogService {
/**
* 保存日志(新事务B)
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog(String message) {
System.out.println("2. 保存日志:" + message);
// 即使这里抛异常,只回滚日志事务,不影响订单事务
INSERT INTO logs VALUES (message);
}
}
执行流程:
createOrder() { // 开启事务A
@Transactional(REQUIRED)
创建订单 ← 在事务A中
saveLog() { // 挂起事务A,开启新事务B
@Transactional(REQUIRES_NEW)
保存日志 ← 在事务B中
} // 提交事务B,恢复事务A
订单创建完成 ← 在事务A中
} // 提交事务A
关键特性:
1. saveLog()开启新事务B
2. 事务A被挂起(暂停)
3. 事务B独立提交/回滚
4. 事务B完成后,恢复事务A
5. 两个事务完全独立
使用场景:
- 日志记录(不应影响业务)
- 审计记录
- 独立的子任务
3️⃣ NESTED ⭐⭐⭐
嵌套事务,使用SavePoint!
/**
* NESTED:嵌套事务
*/
@Service
public class OrderService {
@Autowired
private CouponService couponService;
/**
* 创建订单(外部事务)
*/
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder() {
System.out.println("1. 创建订单");
try {
// 使用优惠券(嵌套事务)
couponService.useCoupon();
} catch (Exception e) {
// 优惠券失败,只回滚优惠券操作
System.out.println("优惠券使用失败,继续创建订单");
}
System.out.println("3. 订单创建完成");
}
}
@Service
public class CouponService {
/**
* 使用优惠券(嵌套事务)
*/
@Transactional(propagation = Propagation.NESTED)
public void useCoupon() {
System.out.println("2. 使用优惠券");
// 优惠券不存在,抛异常
if (coupon == null) {
throw new RuntimeException("优惠券不存在");
}
UPDATE coupons SET status = 'USED';
}
}
执行流程:
createOrder() { // 开启事务A
@Transactional(REQUIRED)
创建订单 ← 在事务A中
设置SavePoint ← 保存点
useCoupon() { // 加入事务A(嵌套)
@Transactional(NESTED)
使用优惠券 ← 在事务A中
抛出异常 ❌
}
回滚到SavePoint ← 只回滚优惠券操作
订单创建完成 ← 在事务A中
} // 提交事务A
关键特性:
1. useCoupon()在外部事务中执行
2. 设置SavePoint保存点
3. useCoupon()失败,回滚到SavePoint
4. 外部事务可以选择捕获异常继续执行
5. 外部事务失败,整个回滚(包括嵌套部分)
4️⃣ SUPPORTS
有事务就加入,没有就非事务执行!
/**
* SUPPORTS:随遇而安
*/
@Service
public class QueryService {
/**
* 查询数据(SUPPORTS)
*/
@Transactional(propagation = Propagation.SUPPORTS)
public List<User> queryUsers() {
// 如果外部有事务,就加入
// 如果外部无事务,就非事务执行
return userMapper.selectAll();
}
}
// 使用场景1:外部有事务
@Transactional
public void businessMethod() {
// queryUsers()加入事务
queryService.queryUsers();
}
// 使用场景2:外部无事务
public void nonTransactionalMethod() {
// queryUsers()非事务执行
queryService.queryUsers();
}
/**
* 适用场景:
* - 查询操作(可以加入事务,也可以不加入)
* - 灵活的方法
*/
5️⃣ MANDATORY
必须有事务,否则抛异常!
/**
* MANDATORY:强制要求事务
*/
@Service
public class PaymentService {
/**
* 支付(必须在事务中)
*/
@Transactional(propagation = Propagation.MANDATORY)
public void pay(Order order) {
// 必须在外部事务中调用,否则抛异常
UPDATE accounts SET balance = balance - order.getAmount();
}
}
// ✅ 正确用法:外部有事务
@Service
public class OrderService {
@Transactional
public void createOrder() {
// 有事务,正常执行
paymentService.pay(order);
}
}
// ❌ 错误用法:外部无事务
@Service
public class OrderService {
public void createOrderWrong() {
// 无事务,抛异常!
// IllegalTransactionStateException:
// No existing transaction found for transaction marked with propagation 'mandatory'
paymentService.pay(order);
}
}
/**
* 适用场景:
* - 关键操作必须在事务中执行
* - 防止误用(强制约束)
*/
6️⃣ NOT_SUPPORTED
总是非事务执行,挂起当前事务!
/**
* NOT_SUPPORTED:不支持事务
*/
@Service
public class ReportService {
/**
* 生成报表(不需要事务)
*/
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void generateReport() {
// 即使外部有事务,也挂起,非事务执行
// 长时间查询,不需要事务
List<Data> data = complexQuery();
// 生成报表
generateExcel(data);
}
}
// 执行流程
@Transactional
public void businessMethod() {
创建订单 ← 在事务中
generateReport() ← 挂起事务,非事务执行
更新状态 ← 恢复事务
}
/**
* 适用场景:
* - 长时间操作(不应占用事务连接)
* - 不需要事务的操作
* - 查询统计
*/
7️⃣ NEVER
必须非事务执行,有事务就抛异常!
/**
* NEVER:绝不使用事务
*/
@Service
public class CacheService {
/**
* 刷新缓存(绝不使用事务)
*/
@Transactional(propagation = Propagation.NEVER)
public void refreshCache() {
// 必须在非事务环境中调用
cache.clear();
cache.putAll(loadData());
}
}
// ✅ 正确用法:外部无事务
public void refresh() {
// 非事务,正常执行
cacheService.refreshCache();
}
// ❌ 错误用法:外部有事务
@Transactional
public void refreshInTransaction() {
// 有事务,抛异常!
// IllegalTransactionStateException:
// Existing transaction found for transaction marked with propagation 'never'
cacheService.refreshCache();
}
/**
* 适用场景:
* - 明确不需要事务的操作
* - 防止误用
*/
🆚 传播行为对比
对比表
| 传播行为 | 外部有事务 | 外部无事务 | 使用频率 | 适用场景 |
|---|---|---|---|---|
| REQUIRED | 加入 | 新建 | ⭐⭐⭐⭐⭐ | 默认选择 |
| REQUIRES_NEW | 新建(挂起外部) | 新建 | ⭐⭐⭐⭐ | 独立事务 |
| NESTED | 嵌套(SavePoint) | 新建 | ⭐⭐⭐ | 部分回滚 |
| SUPPORTS | 加入 | 非事务 | ⭐⭐ | 查询操作 |
| MANDATORY | 加入 | 抛异常 | ⭐ | 强制约束 |
| NOT_SUPPORTED | 非事务(挂起) | 非事务 | ⭐ | 长操作 |
| NEVER | 抛异常 | 非事务 | ⭐ | 明确禁止 |
REQUIRES_NEW vs NESTED
相同点:
- 都可以实现"部分回滚"
不同点:
REQUIRES_NEW:
- 新建独立事务
- 挂起外部事务
- 内外事务完全独立
- 内部异常不影响外部
- 外部异常不影响已提交的内部
NESTED:
- 加入外部事务
- 使用SavePoint
- 内部回滚到SavePoint
- 内部异常可被外部捕获
- 外部异常整个回滚(包括内部)
选择建议:
- 完全独立:REQUIRES_NEW(如日志)
- 可选操作:NESTED(如优惠券)
💻 实战案例
案例1:订单创建(REQUIRED + REQUIRES_NEW)
/**
* 订单创建完整流程
*/
@Service
public class OrderService {
@Autowired
private StockService stockService;
@Autowired
private PaymentService paymentService;
@Autowired
private LogService logService;
/**
* 创建订单(主事务)
*/
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(OrderDTO orderDTO) {
// 1. 创建订单记录
Order order = new Order();
order.setUserId(orderDTO.getUserId());
order.setAmount(orderDTO.getAmount());
orderMapper.insert(order);
// 2. 扣减库存(加入事务,失败整体回滚)
stockService.deductStock(orderDTO.getProductId(), orderDTO.getQuantity());
// 3. 扣减余额(加入事务,失败整体回滚)
paymentService.deductBalance(orderDTO.getUserId(), orderDTO.getAmount());
// 4. 记录日志(独立事务,失败不影响订单)
try {
logService.saveLog("订单创建成功:" + order.getId());
} catch (Exception e) {
log.error("日志记录失败", e);
}
}
}
@Service
public class StockService {
/**
* 扣减库存(REQUIRED)
*/
@Transactional(propagation = Propagation.REQUIRED)
public void deductStock(Long productId, Integer quantity) {
int rows = stockMapper.deductStock(productId, quantity);
if (rows == 0) {
throw new BusinessException("库存不足");
}
}
}
@Service
public class PaymentService {
/**
* 扣减余额(REQUIRED)
*/
@Transactional(propagation = Propagation.REQUIRED)
public void deductBalance(Long userId, BigDecimal amount) {
int rows = accountMapper.deductBalance(userId, amount);
if (rows == 0) {
throw new BusinessException("余额不足");
}
}
}
@Service
public class LogService {
/**
* 保存日志(REQUIRES_NEW)
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog(String message) {
Log log = new Log();
log.setMessage(message);
log.setCreateTime(new Date());
logMapper.insert(log);
}
}
案例2:批量导入(REQUIRES_NEW)
/**
* 批量导入用户
*/
@Service
public class UserImportService {
@Autowired
private UserService userService;
/**
* 批量导入(外部事务)
*/
@Transactional(propagation = Propagation.REQUIRED)
public ImportResult batchImport(List<UserDTO> users) {
int successCount = 0;
int failCount = 0;
for (UserDTO userDTO : users) {
try {
// 每个用户使用独立事务
// 一个失败不影响其他
userService.importUser(userDTO);
successCount++;
} catch (Exception e) {
log.error("导入失败:{}", userDTO, e);
failCount++;
}
}
return new ImportResult(successCount, failCount);
}
}
@Service
public class UserService {
/**
* 导入单个用户(独立事务)
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void importUser(UserDTO userDTO) {
User user = new User();
user.setUsername(userDTO.getUsername());
user.setEmail(userDTO.getEmail());
userMapper.insert(user);
}
}
🎉 总结
核心要点
1. 默认传播行为:REQUIRED
- 最常用
- 有事务加入,无事务新建
2. 独立事务:REQUIRES_NEW
- 适合日志、审计
- 失败不影响主业务
3. 嵌套事务:NESTED
- 适合可选操作
- 部分回滚
4. 其他传播行为
- 了解即可
- 很少使用
选择建议
场景1:普通业务方法
→ REQUIRED(默认)
场景2:日志、审计
→ REQUIRES_NEW(独立事务)
场景3:可选功能(如优惠券)
→ NESTED(嵌套事务)
场景4:查询方法
→ SUPPORTS 或 不加@Transactional
场景5:批量导入
→ 每条记录REQUIRES_NEW
记忆口诀
事务传播七种行为,
场景不同选择异。
REQUIRED是默认,
有事务就加入,
没事务就新建,
最常用的选择。
REQUIRES_NEW最独立,
总是新建挂起外部。
日志审计要用它,
失败不影响主业务。
NESTED是嵌套,
设置SavePoint保存点。
内部失败可回滚,
外部失败全回滚。
SUPPORTS很随和,
有事务就加入,
没事务非事务,
查询操作可以用。
MANDATORY很严格,
必须有事务,
否则就抛异常,
强制约束用得少。
NOT_SUPPORTED不要事务,
挂起外部非事务,
长时间操作用它,
不占事务连接。
NEVER最极端,
绝不要事务,
有事务就异常,
明确禁止来用它!
愿你的事务传播得当,数据一致性无忧! 💾✨