JAVA:Spring 事务失效的技术指南

embedded/2025/1/12 19:13:19/

1、简述

在使用 Spring 框架进行开发时,事务管理是保证数据一致性的重要机制。但在实际项目中,可能会遇到事务失效的问题,这导致数据库操作未能按照预期回滚。本文将分析 Spring 中常见的事务失效原因,并通过代码示例来解释如何规避这些问题。

在这里插入图片描述

2、失效原因

在 Spring 中,事务管理主要依赖 @Transactional 注解。但由于其工作机制,以下几种常见情况会导致事务失效:

  • 方法的可见性:事务方法必须是 public,否则 Spring AOP 代理无法拦截方法调用。
  • 自调用:在同一类中自调用事务方法时,事务不会生效,因为 Spring 事务代理未被触发。
  • 异常类型:@Transactional 默认只会回滚 RuntimeException,而对 CheckedException 不回滚。
  • 非事务管理器配置:未配置事务管理器,导致事务注解无效。
  • 嵌套事务与传播级别:事务传播级别设置不当时,可能导致事务未按预期传播。

以下是每种情况的具体示例和解决方案。

3、失效样例

3.1 方法的可见性
@Service
public class TransactionService {@Transactionalvoid privateTransactionMethod() {// 执行数据库操作// 由于该方法是 private,事务失效}public void execute() {privateTransactionMethod();}
}

在上例中,由于 privateTransactionMethod 是 private,Spring 事务代理无法拦截该方法,从而导致事务失效。

解决方案: 将事务方法声明为 public:

@Transactional
public void publicTransactionMethod() {// 正确的事务方法
}
3.2 自调用问题
@Service
public class TransactionService {@Transactionalpublic void transactionMethodA() {// 执行数据库操作transactionMethodB();}@Transactionalpublic void transactionMethodB() {// 执行数据库操作}
}

在这个例子中,transactionMethodA 调用 transactionMethodB,但由于调用发生在同一类内,Spring AOP 代理不会生效,导致 transactionMethodB 的事务失效。

解决方案: 通过依赖注入,将事务方法放入不同的 Bean 中,或者在当前类中注入自身的代理来调用该方法:

@Service
public class TransactionService {@Autowiredprivate TransactionService selfProxy;@Transactionalpublic void transactionMethodA() {// 使用代理调用事务方法selfProxy.transactionMethodB();}@Transactionalpublic void transactionMethodB() {// 执行数据库操作}
}
3.3 异常类型
@Service
public class TransactionService {@Transactionalpublic void transactionMethod() throws IOException {// 执行数据库操作throw new IOException("Checked Exception"); // 事务不会回滚}
}

在上例中,transactionMethod 抛出 IOException(受检异常),Spring 默认不会回滚事务。

解决方案: 在 @Transactional 注解中设置 rollbackFor 参数,指定需要回滚的异常类型:

@Transactional(rollbackFor = Exception.class)
public void transactionMethod() throws IOException {// 执行数据库操作throw new IOException("Checked Exception");
}
3.4 未配置事务管理器

如果项目未正确配置事务管理器,所有的 @Transactional 注解都会无效。

解决方案: 确保项目中正确配置了事务管理器,以下是一个典型的配置示例:

@Configuration
@EnableTransactionManagement
public class TransactionConfig {@Beanpublic PlatformTransactionManager transactionManager(EntityManagerFactory emf) {return new JpaTransactionManager(emf);}
}
3.5 事务传播级别问题

事务传播级别定义了事务的传播行为,例如嵌套事务等。如果传播级别设置不当,可能导致事务无法回滚。例如:

@Service
public class TransactionService {@Autowiredprivate TransactionalServiceB transactionalServiceB;@Transactionalpublic void transactionMethodA() {// 执行数据库操作transactionalServiceB.transactionMethodB();}
}@Service
public class TransactionalServiceB {@Transactional(propagation = Propagation.REQUIRES_NEW)public void transactionMethodB() {// 执行数据库操作}
}

在上例中,transactionMethodB 使用了 REQUIRES_NEW 传播级别,导致其会创建一个新的事务,独立于 transactionMethodA 的事务。这种情况下,即便 transactionMethodA 回滚,transactionMethodB 也不会回滚。

解决方案: 根据业务需求,合理选择传播级别。一般来说,默认的 Propagation.REQUIRED 足以满足大多数情况。

4、分布式事务

在分布式系统中,事务失效问题更加复杂。单体系统的事务依赖于数据库的 ACID 特性,而在分布式系统中,由于多个微服务和数据库实例参与了同一事务,传统的单节点事务管理方式就不再适用。以下是一些分布式环境中常见的事务失效问题及其解决方案。

在分布式系统中,事务失效的原因通常包括以下几种情况:

  • 服务调用失败:在分布式场景中,一个微服务成功执行了数据库操作,而后续的微服务调用失败,导致数据不一致。
  • 网络延迟和故障:网络分区、延迟或异常中断可能导致事务无法正常提交或回滚。
  • 并发问题:多个服务同时对数据进行操作,可能导致数据状态不同步。
  • 不同的数据源:多个服务依赖不同的数据源,在跨数据源事务时可能出现事务失效问题。

常见的解决方案包括 TCC(Try-Confirm-Cancel)模式、消息队列和 Saga 模式等。

4.1 服务调用失败导致事务不一致(使用 TCC 模式)

问题描述: 假设有两个服务,订单服务(OrderService)和库存服务(InventoryService)。订单服务会创建订单,库存服务则会扣减库存。如果订单服务成功创建订单,但在调用库存服务时失败,那么订单记录就会存在,但库存并未扣减,导致数据不一致。

解决方案: 使用 TCC 模式,TCC 模式将事务分为三个步骤:Try、Confirm 和 Cancel。Try 阶段预留资源(如锁定库存),Confirm 阶段确认操作(如正式扣减库存),Cancel 阶段取消操作(如释放锁定的库存)。

// OrderService 中的 Try 阶段
public boolean tryCreateOrder(Order order) {// 创建订单记录并设置为预留状态order.setStatus("PENDING");orderRepository.save(order);return true;
}// Confirm 阶段
public boolean confirmCreateOrder(Order order) {// 将订单状态更新为已确认order.setStatus("CONFIRMED");orderRepository.save(order);return true;
}// Cancel 阶段
public boolean cancelCreateOrder(Order order) {// 删除或回滚订单orderRepository.delete(order);return true;
}

在库存服务中也会有类似的 Try、Confirm 和 Cancel 操作,以确保操作的原子性。TCC 模式的实现通常依赖于框架(如 Seata、ByteTCC)来自动管理事务的三个阶段。

4.2 使用消息队列解决跨服务事务一致性问题

问题描述: 支付服务(PaymentService)和订单服务(OrderService)之间的事务存在依赖。订单生成后会发送支付请求,而支付完成后需更新订单状态。直接调用可能会因为服务中断或网络问题而导致事务失败。

解决方案: 使用消息队列保证异步一致性。订单服务在生成订单后,将消息发送到消息队列,支付服务订阅该消息并执行支付操作。支付完成后,再通过消息队列通知订单服务更新状态。

// OrderService 中的订单生成逻辑
public void createOrder(Order order) {orderRepository.save(order);// 发送订单已创建的消息messageQueue.send("order.created", order.getId());
}// PaymentService 中的支付逻辑
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {// 处理支付逻辑Payment payment = paymentService.processPayment(event.getOrderId());// 支付成功后,发送支付完成的消息messageQueue.send("payment.completed", event.getOrderId());
}// OrderService 接收支付完成的消息
@EventListener
public void onPaymentCompleted(PaymentCompletedEvent event) {Order order = orderRepository.findById(event.getOrderId());order.setStatus("PAID");orderRepository.save(order);
}

消息队列的使用保证了服务间的异步通信,即便支付服务出现短暂故障,订单的创建仍然成功,支付服务恢复后也会继续处理支付逻辑。

4.3 使用 Saga 模式解决分布式事务

问题描述: 在电商系统中,订单服务、支付服务和库存服务需要进行跨服务的事务操作。假设订单服务和支付服务操作都成功,但库存服务在扣减库存时失败。这样会导致订单已支付,但库存不足的情况。

解决方案: 使用 Saga 模式。Saga 模式是一种分布式事务管理方案,每个服务都独立处理自己的事务,并在出错时通过补偿事务来回滚。例如,如果库存服务失败,支付服务会执行补偿操作(如退款)。

Saga 模式可以使用事件驱动的方式来实现:

// 订单服务
@Transactional
public void createOrder(Order order) {orderRepository.save(order);messageQueue.send("order.created", order.getId());
}// 支付服务
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {Payment payment = paymentService.processPayment(event.getOrderId());messageQueue.send("payment.completed", event.getOrderId());
}// 库存服务
@EventListener
public void onPaymentCompleted(PaymentCompletedEvent event) {boolean success = inventoryService.deductInventory(event.getOrderId());if (success) {messageQueue.send("inventory.deducted", event.getOrderId());} else {messageQueue.send("inventory.failed", event.getOrderId());}
}// 支付服务的补偿逻辑(如库存扣减失败,触发退款)
@EventListener
public void onInventoryFailed(InventoryFailedEvent event) {paymentService.refund(event.getOrderId());
}

在 Saga 模式中,每个操作都是独立的事务。如果某个操作失败,则相应服务会执行补偿操作,从而保证数据的一致性。

5、总结

在使用 Spring 事务管理时,务必了解 @Transactional 的限制和特性,避免事务失效。特别是在方法可见性、自调用、异常类型和传播级别方面需多加注意。在分布式系统中,由于多个服务间存在依赖和网络延迟等问题,事务管理更加复杂。使用 TCC 模式、消息队列和 Saga 模式等解决方案,可以保证分布式事务的一致性。选择具体的解决方案时,需要根据业务场景权衡性能、实时性与一致性。


http://www.ppmy.cn/embedded/153362.html

相关文章

Type-C单口便携显示器-LDR6021

Type-C单口便携显示器是一种新兴的显示设备,它凭借其便携性、高性能和广泛的应用场景等优势,正在成为市场的新宠。以下是Type-C单口便携显示器的具体运用方式: 一、连接与传输 1. **设备连接**:Type-C单口便携显示器通过Type-C接…

20250110doker学习记录

1.本机创建tts环境。用conda. 0.1安装。我都用的默认,你也可以。我安装过一次,如果修复,后面加 -u bash Anaconda3-2024.10-1-Linux-x86_64.sh等待一会。 (base) kt@kt4028:~/Downloads$ conda -V conda 24.9.2 学习资源 Conda 常用命令大全(非常详细)零基础入门到精…

基于Springboot + vue实现的厨艺交流平台

🥂(❁◡❁)您的点赞👍➕评论📝➕收藏⭐是作者创作的最大动力🤞 💖📕🎉🔥 支持我:点赞👍收藏⭐️留言📝欢迎留言讨论 🔥🔥&…

《解锁图像的语言密码:Image Caption 开源神经网络项目全解析》

《解锁图像的语言密码:Image Caption 开源项目全解析》 一、开篇:AI 看图说话时代来临二、走进 Image Caption 开源世界三、核心技术拆解:AI 如何学会看图说话(一)深度学习双雄:CNN 与 RNN(二&a…

ChatGPT加速器:解锁高效智能对话的新工具

随着人工智能技术的飞速发展,智能对话系统如ChatGPT已经在各个领域得到了广泛的应用。无论是在客户服务、教育辅导,还是创意写作中,ChatGPT都表现出了强大的能力。然而,在一些特定的网络环境下,用户访问ChatGPT时可能遇…

Python自学 - 类进阶(迭代器)

<< 返回目录 1 Python自学 - 类进阶(迭代器) 迭代器是一个实现了 __iter__ 和 __next__ 方法的对象。实现这两个方法是Python迭代行为一种约定。   为什么要用迭代器&#xff1f;迭代器的好处是不占内存&#xff0c;它并不会像列表一样&#xff0c;把每个成员都占满。…

使用Struts2遇到的Context[项目名称]启动失败问题解决(Java Web学习笔记)

1 引言 读的书中12.1小节的《下载和安装Struts 2 框架》时&#xff0c;按照书中的方法&#xff0c;手工创建一个Web项目&#xff0c;却启动失败。下面完整复原该问题产生过程。 所用环境为&#xff1a; 名称版本Tomcat8.5.78Java1.8Struts2.3.16 在webapps下创建一个目录te…

git问题

拉取项目代码后&#xff0c;出现 1、找回未commit的代码 2、记录不全&#xff0c;只是显示部分代码记录