news 2026/6/10 18:12:11

这几种方案为 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/10 15:45:07

                            COMSOL MXene超材料吸收器的性能研究:高效能量转换与吸收机制探索

                            comsol MXene超材料吸收器。打开COMSOL的时候&#xff0c;总感觉这软件像是个三维乐高乐园——尤其是当你想用MXene这种二维材料搭个超材料吸收器的时候。先别急着点开电磁波模块&#xff0c;咱们先搞明白MXene这货在微波段的奇葩表现&#xff1a;介电常数实部负数&#xff0c;…

                            作者头像 李华
                            网站建设 2026/6/10 14:41:21

                            乐迪信息:煤矿井下高风险行为识别:AI 摄像机自动预警违规攀爬

                            在煤矿开采这一高危行业中&#xff0c;井下作业环境复杂多变&#xff0c;各类风险隐患无处不在。违规攀爬行为作为其中极具危险性的一种&#xff0c;严重威胁着矿工的生命安全以及煤矿的生产秩序。传统的监管方式往往依赖人工巡查&#xff0c;不仅效率低下、容易出现监管盲区&a…

                            作者头像 李华
                            网站建设 2026/6/10 16:34:38

                            【翻译】【SOMEIP-SD】Page43- Page46

                            文章目录5.1.2.4.7 IPv4 SD Endpoint Option5.1.2.4.7 IPv4 SD Endpoint Option IPv4 SD Endpoint Option 用于发送SOME/IP-SD实例的endpoints信息&#xff0c;同时也预示着该option中包含的IP地址和Port号不能被其他SOMEIP服务端和客户端使用。 SOME/IP-SD实例的作用是在ECU之…

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

                            【MicroPython编程-ESP32篇】-Web页面显示BME280传感器数据

                            Web页面显示BME280传感器数据 文章目录 Web页面显示BME280传感器数据 1、BME280介绍 2、软件准备 3、硬件准备与接线 4、代码实现 4.1 BME280驱动库实现 4.2 连接WiFi 4.3 Web服务器实现 在本文中,将介绍如何将BME280 传感器模块与 ESP32一起使用,并通过MicroPython 固件获取…

                            作者头像 李华
                            网站建设 2026/6/10 14:34:53

                            考虑光伏出力利用率的电动汽车充电站能量调度策略。 程序注释详细 针对间歇性能源利用的问题

                            考虑光伏出力利用率的电动汽车充电站能量调度策略。 程序注释详细 针对间歇性能源利用的问题&#xff0c;构建电动汽车的充放电灵活度指标&#xff0c;用以评估电动汽车参与光伏充电站能量调度的能力&#xff1b; 令充电站在饥饿模式或饱和模式下运行&#xff0c;并根据当前运行…

                            作者头像 李华
                            网站建设 2026/6/10 14:32:35

                            如何用Laravel 13构建动态多模态权限体系:完整代码示例曝光

                            第一章&#xff1a;Laravel 13 的多模态权限控制在现代 Web 应用开发中&#xff0c;权限控制不再局限于简单的角色判断。Laravel 13 引入了多模态权限系统&#xff0c;支持基于角色、策略、能力标签以及上下文环境的复合权限判定机制&#xff0c;使访问控制更加灵活与安全。权限…

                            作者头像 李华