您现在的位置是:亿华云 > 知识
Spring事务长了个腿?轻松掌握技巧告别长事务烦恼!
亿华云2025-10-04 17:53:55【知识】2人已围观
简介来源:JAVA日知录大家好,我是飘渺。今天继续DDD&微服务专栏。在之前的文章 基于DDD的订单创建流程中,我们留下了一个问题:在createOrder()方法中,我将调用远程接口获取购物车详
来源:JAVA日知录
大家好,事务松掌事务我是腿轻飘渺。今天继续DDD&微服务专栏。握技
在之前的巧告文章 基于DDD的订单创建 流程中,我们留下了一个问题:在createOrder()方法中,别长我将调用远程接口获取购物车详情、烦恼远程库存校验、事务松掌事务订单保存放在一个事务中,腿轻显然这并不是握技一个正确的做法,因为它会导致长事务,巧告今天就让我们来解决这个问题。别长

为什么会产生长事务
首先,让我们来分析一下产生长事务的事务松掌事务原因。
在Spring中,腿轻@Transactional注解是握技基于AOP实现的,本质上是在目标方法执行前后进行拦截。在目标方法执行前加入或创建一个事务,在方法执行后,根据实际情况选择提交或回滚事务。
当Spring遇到该注解时,会自动从数据库连接池中获取连接并开启事务,然后绑定到ThreadLocal上,对于@Transactional注解包裹的整个方法都是使用同一个连接。网站模板如果出现耗时的操作,如第三方接口调用、业务逻辑复杂、大批量数据处理等,就会导致占用连接的时间很长,数据库连接一直被占用不释放。一旦类似操作过多,就会导致数据库连接池耗尽。
在开头的实例中,一个事务中执行RPC操作是典型的长事务问题。类似的操作还包括在事务中进行大量数据查询、业务规则处理等。
长事务会产生哪些问题
长事务引发的常见危害有:
数据库连接池被占满,应用无法获取连接资源;容易引发数据库死锁;数据库回滚时间长;在主从架构中会导致主从延时变大。如何避免长事务
既然知道了长事务的危害,那么在开发中如何避免这个问题呢?
很明显,解决长事务的宗旨就是 对事务方法进行拆分,尽量让事务变小,变快,减小事务的颗粒度。
编程式事务
因此,我们可以采用编程式事务替代声明式事务@Transactional。云服务器提供商在Spring项目中,可以注入TransactionTemplate对象,然后手动控制事务范围。改造过后的代码如下所示:
public String createOrder(OrderCreateRequest orderCreateRequest){
// 获取购物车详情ShoppingCartDetailDTO shoppingCartDetailDTO = cartRemoteFacade.queryCheckedCartItemByUserId(orderCreateRequest.getCustomerId());
List
checkInventory(cartItemList);
...
transactionTemplate.executeWithoutResult(status -> {
orderRepository.save(tradeOrder);
eventPublisher.publishEvent(newOrderCreatedEvent(tradeOrder));
});
returnorderSn;
}
然而,这里涉及到另一个问题:在保存订单后我们通过EventPublisher发布了一个事件,让监听者来处理剩下的业务逻辑,(在Dailymart中创建订单后需要进行库存预扣),在默认情况下,Spring的事件监听机制是同步的将代码进行解耦,我们希望库存扣减如果出现失败需要回滚订单,而编程式事务无法控制监听者的事务。因此,在这种场景下并不适合使用编程式事务来处理。
敲黑板:使用编程式事务替代声明式事务是解决长事务最简单的实现方式,在大部分场景下都可以采用。在使用时要注意编程式事务搭配EventPublisher时无法控制监听者的事务。
对方法进行拆分
另外一种常见的处理措施就是将方法进行拆分,将大方法拆成小方法,将不需要事务管理的逻辑与事务操作拆开。
public String createOrder(OrderCreateRequest orderCreateRequest){
// 获取购物车详情ShoppingCartDetailDTO shoppingCartDetailDTO = cartRemoteFacade.queryCheckedCartItemByUserId(orderCreateRequest.getCustomerId());
List
checkInventory(cartItemList);
...
this.saveOrder(tradeOrder)
returnorderSn;
}
@Transactional(rollbackFor = RuntimeException.class)
private void saveOrder(TradeOrder tradeOrder){
orderRepository.save(tradeOrder);
eventPublisher.publishEvent(newOrderCreatedEvent(tradeOrder));
}
在上述代码中,获取购物车详情与库存校验不需要事务,源码库将其与事务方法saveOrder()分开。然而,这样的简单拆分会导致事务不生效。这又涉及到另一个知识点:
@Transactional注解的声明式事务是通过spring aop起作用的,而spring aop需要生成代理对象,直接在同一个类中方法调用使用的还是原始对象,事务不生效。其他几个常见的事务不生效的场景为:
@Transactional 应用在非 public 修饰的方法上@Transactional 注解属性 propagation 设置错误@Transactional 注解属性 rollbackFor 设置错误同一个类中方法调用,导致@Transactional失效异常被catch捕获导致@Transactional失效正确的拆分方法应该使用下面两种:
将方法放入另一个类,如新增一个Manager层,通过Spring注入,这样符合了在对象之间调用的条件。详细说明可以参考我的文章为什么阿里建议给MVC三层架构再加一层Manager层!。
启动类添加@EnableAspectJAutoProxy(exposeProxy = true),方法内使用AopContext.currentProxy()获得代理类,使用事务。
SpringBootApplication.java
@EnableAspectJAutoProxy(exposeProxy = true)
@SpringBootApplicationpublic class SpringBootApplication{ }
public String createOrder(OrderCreateRequest orderCreateRequest){
...
OrderService orderService = (OrderService)AopContext.currentProxy();
orderService.saveData(tradeOrder);
returnorderSn;
}
@Transactional(rollbackFor = RuntimeException.class)
private void saveOrder(TradeOrder tradeOrder){
orderRepository.save(tradeOrder);
eventPublisher.publishEvent(newOrderCreatedEvent(tradeOrder));
}
然而,Dailymart项目是基于DDD的分层架构模型实现。原来的业务逻辑是在应用服务编写,在我们项目中只需要将保存订单的逻辑放在领域服务层,由领域服务保证事务,而应用服务层负责组装业务逻辑。最终代码如下:
private finalTradeOrderService tradeOrderService;
@Override// @Transactional(rollbackFor = RuntimeException.class) public String createOrder(OrderCreateRequest orderCreateRequest){
// 生成订单编号String orderSn = IdUtils.nextIdStr();
// 获取购物车详情ShoppingCartDetailDTO shoppingCartDetailDTO = cartRemoteFacade.queryCheckedCartItemByUserId(orderCreateRequest.getCustomerId());
List
checkInventory(cartItemList);
...
tradeOrderService.save(tradeOrder);
returnorderSn;
}
@Service@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class TradeOrderServiceImpl implements TradeOrderService{
private finalApplicationEventPublisher eventPublisher;
private finalOrderRepository orderRepository;
@Override @Transactional public void save(TradeOrder tradeOrder){
orderRepository.save(tradeOrder);
eventPublisher.publishEvent(newOrderCreatedEvent(tradeOrder));
}
}
小结
本文讨论了长事务的危害及解决方案。首先,我们探讨了长事务导致的问题,包括数据库连接池耗尽、死锁等。其次,介绍了两种解决策略:采用编程式事务和对方法进行拆分。编程式事务提供了手动控制事务范围的方式,但需要注意搭配EventPublisher可能导致监听者事务无法控制的问题。对方法进行拆分是一种更通用的方法,能够减小事务范围,提高执行效率。最后,通过实际的DDD分层架构示例,展示了在应用服务层和领域服务层中如何组织业务逻辑,确保事务正确性和性能。
很赞哦!(49)
相关文章
- 四、配置网站,填充内容
- 实现量子计算产业化,量旋科技邀您一起见证量子计算的新未来
- 什么是边缘数据中心,为什么它们对 5G 至关重要?
- 2023年影响数据中心安全的五大风险
- 最后提醒我们,域名到期后要及时更新域名,否则可能会丢掉域名,每次抢先注册都不会成功。
- 联想百应&长沙电信打造ICT服务新模式,IT运维服务助力运营商深耕“两线”
- 构建监控分析可以改善数据中心运营的五种方式
- 扩展Nginx的无限可能:掌握常见扩展模块和第三方插件的使用方法
- 互联网其实拼的也是人脉,域名投资也是一个时效性很强的东西,一个不起眼的消息就会引起整个域名投资市场的动荡,因此拓宽自己的人脉圈,完善自己的信息获取渠道,让自己能够掌握更为多样化的信息,这样才更有助于自己的域名投资。
- 实现量子计算产业化,量旋科技邀您一起见证量子计算的新未来
站长推荐
众所周知,com域名拥有最大的流通市场和流通历史。最好选择com域名,特别是在购买域名时处理域名。其次可以是cn域名、net域名、org域名等主流域名,现在比较流行的王域名和顶级域名,都是值得注册和投资的。
智能PDU的主要有哪些实用功能?
创建绿色数据中心的三个关键战略
企业如何通过五个重要提示发现和雇佣紧缺的数据中心技术人才
为了避免将来给我们的个人站长带来的麻烦,在选择域名后缀时,我们的站长最好省略不稳定的后缀域名,比如n,因为我们不知道策略什么时候会改变,更不用说我们将来是否还能控制这个域名了。因此,如果站长不是企业,或者有选择的话,如果不能选择域名的cn类,最好不要选择它。
冯丹教授:近数据处理新型盘框等技术创新,加速数据中心向Diskless架构演进
今年服务器出货量将下降20%,但AI热潮仍为厂商贡献可观利润
如何改造数据中心以提高效率和可持续性