news 2026/6/15 11:49:54

这几种方案为 Spring Boot 事务与外部服务协同

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
这几种方案为 Spring Boot 事务与外部服务协同

点击上方“程序员蜗牛g”,选择“设为星标”

跟蜗牛哥一起,每天进步一点点

程序员蜗牛g

大厂程序员一枚 跟蜗牛一起 每天进步一点点

33篇原创内容

公众号

在分布式系统里,Spring Boot事务管理边界处理是架构设计的一大痛点。

关键业务涉及数据库事务与第三方服务调用(如邮件发送、远程接口调用)混合场景时,开发者常陷入两难:

在 @Transactional 中直接调用,网络问题会使整个事务回滚致订单丢失;

移至事务外,又会出现数据不一致风险。

本文将以4层渐进式方案,深度剖析Spring Boot事务与外部服务的协同策略。

从基础的事务内阻塞调用,逐步进阶至本地消息表,共给出4个方案。

每个方案均附完整代码,且会揭示前代方案的不足,带你领略技术演进之路。

2.实战案例

环境准备

    // 订单对象@Entity@Table(name = "x_order")public class Order {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id ;private String orderNo;private BigDecimal amount;private Integer status ;private LocalDateTime orderTime;}

    订单&邮件基本操作类

      public interface OrderRepository extends JpaRepository<Order, Long> {}@Servicepublic class EmailService {public void sendEmail(Order order) {System.err.printf("给【%s】发送邮件成功, 本次订单总额: %s%n",UserContext.getEmail(), order.getAmount()) ;}}@Servicepublic class OrderService {private final OrderRepository orderRepository ;private final EmailService emailService ;@Transactionalpublic void createOrder(Order order) {// 各种方案实现}}

      2.1 方案1:事务方法内直接调用

      在事务方法中直接调用邮件发送(或其它操作),代码简单但隐患巨大。适用于快速原型验证,但生产环境严禁使用。

        @Transactionalpublic void createOrder(Order order) {// 1.保存订单orderRepository.save(order) ;// 2.发送emailService.sendEmail(order) ;}

        问题分析:

        • 事务膨胀:邮件调用耗时过长会占用数据库连接,降低并发性能

        • 事务回滚污染:若邮件发送失败抛异常,会导致整个事务回滚

        • 可靠性问题:网络波动可能使邮件发送失败,无法重试

        • 耦合性高:业务逻辑与通知逻辑紧耦合

        2.2 方案2:事务钩子回调

        通过Spring事务同步器在事务提交后触发外部调用,避免事务回滚污染。适合对实时性要求低、调用量小的场景,如内部系统通知。

          private final ApplicationEventPublisher eventPublisher;@Transactionalpublic void createOrder(Order order) {orderRepository.save(order);// 发布事件注册事务钩子回调this.eventPublisher.publishEvent(new OrderCreatedEvent(order)) ;}

          事件对象&事件监听

            public class OrderCreatedEvent extends ApplicationEvent{public OrderCreatedEvent(Object source) {super(source);}}@Componentpublic class OrderEventListener {private final EmailService emailService;public OrderEventListener(EmailService emailService) {this.emailService = emailService;}// 事务提交以后执行@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)public void handleOrderCreatedEvent(OrderCreatedEvent event) {this.emailService.sendEmail((Order) event.getSource()) ;}}

            问题分析:

            • 同步执行瓶颈:监听器与主线程同步执行,响应延迟

            • 无重试机制:临时故障导致永久失败

            • 事件丢失可能:应用重启时未处理事件会丢失

            2.3 方案3:异步+事务钩子回调

            结合 @Async 异步执行和 @Retryable 自动重试,解决同步阻塞和临时故障问题。但仍依赖应用内存,崩溃时事件丢失,适合要求不是很严格的业务场景。

            首先,引入依赖

              <dependency><groupId>org.springframework.retry</groupId><artifactId>spring-retry</artifactId></dependency>

              其次,开启异步&重试机制

                @Configuration@EnableAsync@EnableRetrypublic class AsyncConfig implements AsyncConfigurer {@Overridepublic Executor getAsyncExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor() ;executor.setThreadNamePrefix("Pack-Async-");executor.setCorePoolSize(5) ;executor.setMaxPoolSize(10) ;executor.setQueueCapacity(100) ;executor.initialize() ;return executor ;}}

                最后,修改事务提交后监听

                  @Async@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)@Retryable(retryFor = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 1000))public void handleOrderCreatedEvent(OrderCreatedEvent event) {this.emailService.sendEmail((Order) event.getSource()) ;}

                  修改邮件发送模拟错误

                    public void sendEmail(Order order) {if (new Random().nextInt(2) == 1) {throw new RuntimeException("State Error") ;}System.err.printf("%s - 给【%s】发送邮件成功, 本次订单总额: %s%n",Thread.currentThread().getName(), UserContext.getEmail(), order.getAmount()) ;}

                    测试结果

                    重试第三次后成功。

                    问题分析:

                    • 消息丢失风险:应用崩溃时内存中的事件会丢失

                    • 重试局限性:超过最大重试次数后仍失败问题

                    • 未持久化:最终失败的操作无法人工干预

                    2.4 方案4:本地消息表

                    通过数据库事务原子性保存任务记录,定时任务异步处理,确保消息不丢失。支持无限重试和人工干预,实现最终一致性。

                    创建本地消息表对象&Repository

                      @Entity@Table(name = "x_local_message")public class LocalMessage {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;// JSON格式的任务数据@Column(length = 500)private String payload;/**1:处理中,2:失败,3:成功*/@Column(columnDefinition = "int default 0")private Integer state ;private LocalDateTime createdAt = LocalDateTime.now() ;}public interface LocalMessageRepository extends JpaRepository<LocalMessage, Long>{@Query("select e from LocalMessage e where e.state = 1 and e.retryCount < 3 order by e.createdAt desc limit 10")List<LocalMessage> queryMessages() ;}

                      修改创建订单业务

                        @Transactionalpublic void createOrder(Order order) {this.orderRepository.save(order);LocalMessage message = new LocalMessage();message.setState(1);try {message.setPayload(this.objectMapper.writeValueAsString(order));} catch (Exception e) {}this.messageRepository.save(message);}

                        定义定时任务

                        首先,开启定时任务

                          @Configuration@EnableSchedulingpublic class TaskConfig {}

                          最后,定义定时任务

                            @Componentpublic class TaskService {private final ExecutorService executor = Executors.newFixedThreadPool(5);private final LocalMessageRepository messageRepository ;private final EmailService emailService ;private final ObjectMapper objectMapper ;public TaskService(LocalMessageRepository messageRepository,EmailService emailService, ObjectMapper objectMapper) {this.messageRepository = messageRepository;this.emailService = emailService ;this.objectMapper = objectMapper ;}@Scheduled(cron = "0 */2 * * * ?")public void sendMailTask() {List<LocalMessage> messages = this.messageRepository.queryMessages() ;List<CompletableFuture<Void>> futures = new ArrayList<>(messages.size());messages.forEach(message -> {CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {try {Order order = this.objectMapper.readValue(message.getPayload(), Order.class);this.emailService.sendEmail(order);message.setState(3);message.setRetryCount(message.getRetryCount() + 1);} catch (Exception e) {int retryCount = message.getRetryCount() + 1;if (retryCount >= 3) {message.setState(2);}message.setRetryCount(retryCount);} finally {messageRepository.save(message);}}, executor);futures.add(future);});CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join() ;}}

                            问题分析:

                            • 实时性:任务执行需等到下一轮才能执行

                            本方案优势:

                            • 可靠性:数据库事务保证任务持久化

                            • 故障恢复:定时任务自动重试失败操作

                            • 系统解耦:业务服务与邮件发送完全隔离

                            如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、转发、在看。

                            关注公众号:woniuxgg,在公众号中回复:笔记 就可以获得蜗牛为你精心准备的java实战语雀笔记,回复面试、开发手册、有超赞的粉丝福利

                            版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
                            网站建设 2026/6/15 7:47:12

                            精准度量与高效提升:软件测试覆盖率的系统化实践路径

                            测试覆盖率的双重价值与当代挑战测试覆盖率作为衡量软件测试完备性的关键指标&#xff0c;在当今快速迭代的软件开发环境中扮演着至关重要的角色。它不仅是评估测试用例设计充分性的量化工具&#xff0c;更是识别未被测试的代码区域、发现潜在缺陷的有效手段。然而&#xff0c;…

                            作者头像 李华
                            网站建设 2026/6/15 14:00:04

                            车间实战笔记:1200线体设备如何玩转V90全家桶

                            出口设备1200线体程序&#xff0c;多个plc走通讯&#xff0c;内部有多个v90,采用工艺对象与fb284 共同控制&#xff0c;功能快全部开源&#xff0c;能快速学会v90的控制&#xff0c; 最近刚交付的出口设备项目里&#xff0c;一套1200PLC带着8个V90伺服满场飞。老铁们都知道&am…

                            作者头像 李华
                            网站建设 2026/6/15 3:31:57

                            资深工程师亲授:行为树调试与优化的6步黄金流程

                            第一章&#xff1a;行为树的优化在复杂的游戏AI或自动化系统中&#xff0c;行为树&#xff08;Behavior Tree&#xff09;作为决策核心组件&#xff0c;其执行效率直接影响整体性能。随着节点数量增加和逻辑嵌套加深&#xff0c;未优化的行为树可能导致严重的性能瓶颈。因此&am…

                            作者头像 李华
                            网站建设 2026/6/15 19:12:36

                            最近在工控项目里折腾了一把信捷XD5 PLC和台达DT330温控器的通讯,整个过程就像玩解谜游戏——接线、调参数、写程序环环相扣。直接上干货,先看核心通讯程序

                            信捷XD PLC与台达DT330温控器通讯程序输出启停控制(XJXD-1)功能&#xff1a;通过信捷XD5&#xff0c;实现对台达DT330温控器 设定温度&#xff0c;读取温度&#xff0c;控制温控器输出启停&#xff0c;反应灵敏&#xff0c;通讯稳定可靠。 程序采用轮询方式器件&#xff1a;信捷…

                            作者头像 李华
                            网站建设 2026/6/14 22:46:30

                            dify 创建gitlab账号

                            目录 1、环境: 2、获取gitlab访问令牌 3、dify安装[JSON 处理]插件 ​4、dify创建工作流应用 5、dify详细配置 6、校验 1、环境 dify版本Version 1.5.1 gitlab版本号:gitlab企业版16.10 完成配置的工作流截图。 工作流导出的DSL:创建gitlab账号demo.yml 链接: https…

                            作者头像 李华
                            网站建设 2026/6/15 11:42:23

                            Carsim Simulink联合仿真-基于LQR/模糊PID/滑模控制的横摆稳定性控制系统

                            Carsim Simulink联合仿真-基于LQR/模糊PID/滑模控制的横摆稳定性控制系统 综合跟随理想横摆角速度的方法和抑制汽车质心侧偏角的汽车稳定性控制方法&#xff0c;以线性二自由度车辆操纵特性模型为控制目标&#xff0c;基于汽车横摆力矩与车辆状态偏差之间的动力学关系建立了控制…

                            作者头像 李华