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