当前位置: 首页 > news >正文

多线程 + 事务传播误用导致的问题

典型且具有代表性的 SaaS OA 系统中 多线程 + 事务传播误用 导致问题,最终通过 PROPAGATION_REQUIRES_NEW 解决的场景。


🧩 背景:SaaS OA 系统的“批量审批”功能

  • 系统类型:SaaS 架构的 OA 系统,支持多个租户(Tenant)。
  • 功能需求:管理员可以批量审批多个请假申请(Batch Approve Leave Requests)。
  • 性能要求:为了提升审批速度,系统使用 多线程并行处理 每个请假单。
  • 数据一致性要求
    • 每个请假单的审批要记录日志。
    • 审批成功后要更新请假单状态,并通知 HR 系统。
    • 即使某个请假单处理失败,也不能影响其他请假单的提交。

❌ 问题场景:事务传播属性误用导致报错

原始错误代码(使用 PROPAGATION_REQUIRED
@Service
public class BatchApprovalService {@Autowiredprivate LeaveService leaveService;@Autowiredprivate ApprovalLogService approvalLogService;// 外层方法开启事务@Transactionalpublic void batchApprove(List<Long> leaveIds, String approver) {List<CompletableFuture<Void>> futures = new ArrayList<>();for (Long leaveId : leaveIds) {// 使用线程池并行处理CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {try {// ❌ 问题所在:每个线程内调用的方法也使用 REQUIREDleaveService.approveLeave(leaveId, approver);} catch (Exception e) {log.error("审批请假单 {} 失败", leaveId, e);// 不抛出,避免阻塞其他任务}}, taskExecutor); // 自定义线程池futures.add(future);}// 等待所有任务完成CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();}
}@Service
public class LeaveService {@Autowiredprivate LeaveMapper leaveMapper;@Autowiredprivate ApprovalLogService approvalLogService;// 使用默认 REQUIRED,依赖外层事务@Transactionalpublic void approveLeave(Long leaveId, String approver) {// 1. 查询请假单Leave leave = leaveMapper.selectById(leaveId);if (leave == null) throw new RuntimeException("请假单不存在");// 2. 更新状态leave.setStatus("APPROVED");leave.setApprover(approver);leaveMapper.updateById(leave);// 3. 记录审批日志(关键!)ApprovalLog log = new ApprovalLog(leaveId, approver, "APPROVED");approvalLogService.saveLog(log); // 内部也是 REQUIRED}
}@Service
public class ApprovalLogService {@Autowiredprivate ApprovalLogMapper logMapper;@Transactional // 默认 REQUIREDpublic void saveLog(ApprovalLog log) {logMapper.insert(log);}
}

⚠️ 报错现象

运行时出现如下错误:

org.springframework.transaction.IllegalTransactionStateException: 
Participating in existing transaction with name [xxx], but configured with propagation 'REQUIRES_NEW'. 
However, this error is more subtle...// 更常见的是:
java.lang.IllegalStateException: 
Transaction synchronization is not activeat org.springframework.transaction.support.AbstractTransactionSynchronizationManager.getResource(AbstractTransactionSynchronizationManager.java:132)

或者:

“No transaction aspect-managed TransactionStatus in scope”


🔍 根本原因分析

  1. 外层 @Transactional 开启事务 → 事务绑定在 主线程ThreadLocal 中。
  2. CompletableFuture.runAsync 创建新线程 → 子线程无法继承主线程的事务上下文。
  3. 子线程中调用 @Transactional 方法(approveLeave)→ Spring 尝试加入“当前事务”,但子线程没有事务上下文 → 事务失效或报错
  4. 即使不报错,一旦某个子线程抛异常,整个外层事务可能回滚,导致其他已成功处理的请假单也被回滚,违反“独立处理”需求。

✅ 正确解决方案:使用 PROPAGATION_REQUIRES_NEW

修改 LeaveService.approveLeave 方法的事务传播行为:

@Service
public class LeaveService {@Autowiredprivate LeaveMapper leaveMapper;@Autowiredprivate ApprovalLogService approvalLogService;// ✅ 关键修改:使用 REQUIRES_NEW@Transactional(propagation = Propagation.REQUIRES_NEW)public void approveLeave(Long leaveId, String approver) {try {Leave leave = leaveMapper.selectById(leaveId);if (leave == null) throw new RuntimeException("请假单不存在");leave.setStatus("APPROVED");leave.setApprover(approver);leaveMapper.updateById(leave);ApprovalLog log = new ApprovalLog(leaveId, approver, "APPROVED");approvalLogService.saveLog(log); // 这个方法也应是 REQUIRES_NEW 或 REQUIRED} catch (Exception e) {// ✅ 即使失败,只回滚当前事务,不影响其他线程log.error("审批请假单 {} 失败", leaveId, e);throw e; // 抛出,由上层记录}}
}

同时,外层方法不再需要事务(或可以去掉):

@Service
public class BatchApprovalService {@Autowiredprivate LeaveService leaveService;// ❌ 不再需要 @Transactional// 因为每个子任务独立提交public void batchApprove(List<Long> leaveIds, String approver) {List<CompletableFuture<Void>> futures = new ArrayList<>();for (Long leaveId : leaveIds) {CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {try {// 每个调用都是独立事务leaveService.approveLeave(leaveId, approver);} catch (Exception e) {log.error("审批失败,但不影响其他", e);// 可记录失败日志}}, taskExecutor);futures.add(future);}CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();}
}

✅ 为什么 REQUIRES_NEW 能解决问题?

问题REQUIRED 表现REQUIRES_NEW 表现
事务上下文继承子线程无事务上下文 → 报错或失效忽略外层,自己开启新事务 → ✅ 成功
异常影响范围一个失败 → 外层回滚 → 全部失败一个失败 → 仅自己回滚 → ❌ 其他继续
性能与隔离所有操作串行等待事务每个线程独立提交 → ✅ 高并发
日志可靠性日志可能随事务回滚丢失日志独立提交 → ✅ 永久记录

🏁 最终效果

  • ✅ 每个请假单在独立线程中处理,互不影响。
  • ✅ 每个处理过程有独立事务,提交或回滚不影响他人。
  • ✅ 审批日志可靠记录,即使业务失败也不会丢失。
  • ✅ 符合 SaaS 系统中“租户/任务隔离”的设计原则。
  • ✅ 系统吞吐量提升,用户体验更好。

📌 总结:SaaS + 多线程 + 事务的经典模式

// 外层:不加事务,或只用于非核心操作
public void batchProcess(...) {for (...) {CompletableFuture.runAsync(() -> {// 内层:每个任务使用 REQUIRES_NEWservice.processInNewTransaction(...);});}
}

核心思想:在多线程异步处理中,避免共享事务上下文,使用 PROPAGATION_REQUIRES_NEW 实现任务级别的事务隔离与可靠性,是 SaaS 系统中高并发、高可用场景的推荐实践。

http://www.dtcms.com/a/341385.html

相关文章:

  • 【北京迅为】iTOP-4412精英版使用手册-第三十二章 网络通信-TCP套字节
  • 如何排查服务器DNS解析失败的问题
  • TypeScript中的枚举
  • UE5分享序列播放器的停止与设置播放范围
  • 8.20作业
  • [Mysql数据库] 用户管理选择题
  • IIS访问报错:HTTP 错误 500.19 - Internal Server Error
  • rust语言 (1.88) egui (0.32.1) 学习笔记(逐行注释)(一)基本代码
  • python的校园顺路代送系统
  • Seaweed-APT:AI视频生成模型,单步生成2秒钟的1280x720 24fps视频
  • 46.安卓逆向2-补环境-使用unidbg(使用apk文件补环境)
  • 面试记录5 .net
  • 电商大数据的采集过程详解​【采集内容|采集渠道|采集步骤|注意事项】
  • 算法第34天|动态规划:打家劫舍Ⅰ、打家劫舍Ⅱ、打家劫舍Ⅲ
  • 为了更强大的空间智能,如何将2D图像转换成完整、具有真实尺度和外观的3D场景?
  • (双类别检测:电动车 + 头部,再对头部分类)VS 单类别检测 + ROI 分类器 方案
  • 小迪安全v2023学习笔记(六十七讲)—— Java安全JNDI注入五大不安全组件RCE不出网
  • 2025年中高级后端开发Java岗八股文最新开源
  • 利用 PHP 爬虫获取店铺所有商品实战指南
  • Spring Boot 3.4.x 性能优化实战:用 Undertow 替换 Tomcat 全指南​
  • 自动驾驶汽车机器学习安全实用解决方案
  • 三坐标性能的创新重构,“高精度、紧凑型、高稳定性”三位一体
  • 鸿蒙中Profiler的使用
  • STM32学习笔记16-SPI硬件控制
  • MySQL 语法基础入门:从零开始掌握数据库操作
  • CoreShop微信小程序商城框架开启多租户-添加一个WPF客户端以便进行本地操作(5)
  • 读《精益数据分析》:规模化(Scale)—— 复制成功,进军新市场
  • VMware Workstation | 安装Ubuntu18.04.5
  • 波纹干涩 shader
  • 零知开源——基于STM32F103RBT6与ADXL362三轴加速度计的体感迷宫游戏设计与实现