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/9 22:45:16

                            大数据获客系统:技术赋能下的精准营销革命与架构实践

                            在数字化浪潮席卷各行各业的今天&#xff0c;企业获取新客户&#xff08;获客&#xff09;的成本持续攀升&#xff0c;传统广撒网式的营销模式效率低下&#xff0c;投资回报率&#xff08;ROI&#xff09;难以保障。企业面临着海量数据却无从下手的困境&#xff0c;如何从纷繁复…

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

                            别再让SaveChanges拖垮系统!提升EF Core写入性能的6种方法

                            第一章&#xff1a;EF Core 写入性能问题的根源剖析Entity Framework Core&#xff08;EF Core&#xff09;作为.NET平台主流的ORM框架&#xff0c;极大简化了数据访问逻辑的开发工作。然而在高并发或大批量数据写入场景下&#xff0c;开发者常遭遇性能瓶颈。这些问题并非源于框…

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

                            为什么你的协程 silently 崩溃?深入剖析纤维异常未捕获根源

                            第一章&#xff1a;协程异常静默崩溃的典型场景在现代异步编程中&#xff0c;协程&#xff08;Coroutine&#xff09;因其轻量级和高并发特性被广泛使用。然而&#xff0c;当协程内部发生未捕获的异常时&#xff0c;往往不会导致主线程崩溃&#xff0c;而是以“静默崩溃”的方式…

                            作者头像 李华
                            网站建设 2026/6/10 2:18:49

                            【独家解析】PHP 8.6扩展依赖模型重构背后的底层逻辑

                            第一章&#xff1a;PHP 8.6 扩展依赖管理的演进背景PHP 8.6 的发布标志着语言在模块化和可维护性方面迈出了关键一步&#xff0c;特别是在扩展依赖管理机制上的改进。随着 PHP 生态日益复杂&#xff0c;开发者对扩展间版本兼容性和自动解析能力的需求愈发迫切。传统上&#xff…

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

                            分布式驱动电动汽车的最优直接横摆力矩控制与规则扭矩分配控制策略:基于LQR计算与最小附着利用率...

                            分布式驱动电动汽车 直接横摆力矩控制 最优/规则扭矩分配控制 上层lqr计算 下层最小附着利用率分配 扭矩分配 对比传统esc 效果优良 稳定性控制 操纵稳定性 matlab simulink代码源码 carsim联合仿真我最近在倒腾分布式驱动电动车的稳定性控制&#xff0c;发现这玩意儿比传统燃油…

                            作者头像 李华