这几种方案为 Spring Boot 事务与外部服务协同
2026/5/23 9:32:58 网站建设 项目流程

点击上方“程序员蜗牛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实战语雀笔记,回复面试、开发手册、有超赞的粉丝福利

                            需要专业的网站建设服务?

                            联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

                            立即咨询