@Transactional 你真的用对了吗?

简介: 在日常开发中,`@Transactional`注解常用于声明式事务管理,但其原理和使用不当可能引发问题。本文通过一个实际场景探讨了自调用方法时事务不生效的问题,并分析了潜在风险:数据不一致。为解决此问题,提供了三种方案:1) 将方法移动到其他服务类;2) 使用`AopContext.currentProxy()`获取代理对象;3) 通过`ApplicationContext`获取Bean。最终建议尽量避免自调用事务操作,确保数据一致性。

场景

我们在日常开发中几乎经常会使用@Transactional对方法或者接口进行事务管理,毕竟声明式事务这么方便,谁不爱呢,哈哈!但是这个注解的实现原理我们需要搞清楚才能避免“踩坑”。本篇文章就是针对一个使用场景来讨论的,希望可以帮助大家避免在开发中犯下这个错。

1.png

某天我在做自己的需求时,发现有一个同事写的老接口(Ps:此处真的是我有一个同事😶)里的一行代码IDE有个警告提示,本来也没在意,以为就是个格式检查,但是瞄了一眼,发现不对劲,提示的是:

"@Transactional 自调用(实际上是目标对象内的方法调用目标对象的另一个方法)在运行时不会导致实际的事务。"
这和事务扯上关系了,那我当然要好好掰头一下,于是我看了一下他的代码,大概是这样(不能暴露业务代码,我搞个伪代码给大家看看):

@Override
public void updateA(List<Things> thingList) {
   
  ...
  updateB(thingList);
}

在这个代码里,updateA 方法里调用了 updateB 方法,它们在各自的接口上都@Transactional注解,但是这个调用又是在当前同一个类下进行的,所以不会开启新的事务,进而也就有了IDE的这个提示。

了解风险

既然已经发现了这个问题出现的场景,那么我们也需要明确这样使用有什么风险呢?其实从IDE的警告来看就已经十分清晰了,关注最后一句“在运行时不会导致实际的事务”,意思就是有一个方法的事务并不会生效,那么就有数据不一致的风险了。
2.png
可能有同学还是有点懵,我举个例子:假设你有一个事务,它包含两个操作:操作A和操作B。如果操作A成功,但操作B失败,那么操作A的结果将被回滚,但操作B由于声明式事务未生效,即未被事务管理,那么它将不会被回滚,但这样如果两个操作涉及到同一数据的处理就会造成不一致的问题了。

解决方案

当然了,作为一个有点东西的专业开发,我们不能只提出问题,而不解决问题嘛,针对这个场景,我们可以有下面三种方案来解决问题:
3.png

方案一:移动大法好

这里我们不提在controller层注入service、service层注入mapper之类的办法,满足有的同学不想动业务代码这一需求,来解决这一问题。既然问题的根因在于Spring 的事务管理默认是基于代理的,只有通过代理对象调用的方法才会被 Spring 的事务管理器捕获并处理。 那么就把其中一个方法在另一个服务类中声明,然后在当前类中注入并调用。这样,它的调用就会被Spring的事务管理器捕获,并在新的事务中运行。

方案二:巧借AOP

使用AopContext.currentProxy()来获取当前的代理对象,然后通过这个代理对象来调用方法。这样,被调用的方法就会被Spring的事务管理器捕获,并在新的事务中运行。但是,这种方法需要在Spring的配置中启用exposeProxy属性。

(YourService) 
AopContext.currentProxy().updateB(thingList);

方案三:IOC容器来帮你

使用ApplicationContext来获取当前的Bean,然后通过这个Bean来调用方法。这样,被调用的方法就会被Spring的事务管理器捕获,并在新的事务中运行。但是,这种方法需要在你的类中注入ApplicationContext

@Autowired
private ApplicationContext applicationContext;

...

applicationContext.getBean("yourService")).updateB(thingList);

注意

以上三种方法的作用是相同的,都会让每个方法都会被其自己的事务管理。具体来说,当你通过Spring的代理对象或者Bean来调用一个方法时,Spring的事务管理器会为这个方法开启一个新的事务。这个新的事务是独立于当前事务的,也就是说,它有自己的事务边界,可以独立地提交或者回滚。
所以,如果A和B操作都对数据库中的同一行数据进行操作,并且它们是在两个不同的事务中执行的,那么如果B操作失败并回滚,A操作不会被回滚。这可能会导致数据的一致性问题。
因此,最终想要两个方法被一个事务管理,还是得通过在一个方法中调用A和B操作来实现这一点,并在这个方法上使用@Transactional注解。

@Transactional
public void updateData() {
   
    operationA();
    operationB();
}

小结

方法是死的,人是活的。大家还是要根据自己实际的业务场景选择合适的方式,当然,尽量还是避免出现这种自调用的事务操作!

目录
相关文章
|
Java 编译器 数据库
@Transactional中指定rollbackFor,弊端以及不能回滚的时候
@Transactional中指定rollbackFor,弊端以及不能回滚的时候
928 3
|
测试技术
apifox的并发处理—通过动态变量实现
apifox的并发处理—通过动态变量实现
apifox的并发处理—通过动态变量实现
|
消息中间件 Kafka 流计算
Flink读取Kafka报Error sending fetch request
实时计算Flink读取消息队列Kafka,flink日志中出现Error sending fetch request (sessionId=1510763375, epoch=12890978) to node 103: {}. org.apache.flink.kafka.shaded.org.apache.kafka.common.errors.DisconnectException: null
13485 3
Flink读取Kafka报Error sending fetch request
|
6月前
|
人工智能 NoSQL Java
Spring AI 进阶之路03:集成RAG构建高效知识库
本文介绍如何在Spring Boot中集成RAG(检索增强生成)技术,通过Redis向量数据库为大模型外挂私域知识库。手把手实现文档上传、切分、向量化存储,并构建支持普通对话与知识库问答双模式的智能聊天机器人,解决大模型对私有信息无知的问题,助力打造企业级AI应用。
1895 1
|
11月前
|
存储 消息中间件 NoSQL
跟着大厂学架构01:如何利用开源方案,复刻B站那套“永不崩溃”的评论系统?
本文基于B站技术团队分享的《B站评论系统的多级存储架构》,解析其在高并发场景下的设计精髓,并通过开源技术栈(MySQL、Redis、Java)复刻其实现。文章深入讲解了多级存储、数据同步、容灾降级等关键设计,并附有完整代码实现,助你掌握大厂架构设计之道。
573 0
|
人工智能 算法 异构计算
用“吃火锅”来讲清楚大模型是什么
大语言模型就像一个超级聪明的“火锅AI服务员”,它通过海量数据训练,能根据你的需求推荐菜品、回答问题、甚至陪你聊人生哲学。它有超强的记忆力和灵活的应变能力,能接住各种奇葩问题,还会跟你玩梗互动。虽然偶尔会瞎编答案,但它绝对是个知识型选手。本文用轻松的“火锅局”方式,带你深入了解大语言模型的工作原理和特点。
394 1
|
Kubernetes 测试技术 数据库
详解微服务应用灰度发布最佳实践
相对于传统软件研发,微服务架构下典型的需求交付最大的区别在于有了能够小范围真实验证的机制,且交付单位较小,风险可控,灰度发布可以弥补线下测试的不足。本文从 DevOps 视角概述灰度发布实践,介绍如何将灰度发布与 DevOps 工作融合,快来了解吧~
34050 19
|
存储 Java API
SpringBoot整合Flowable【02】- 整合初体验
本文介绍了如何基于Flowable 6.8.1版本搭建工作流项目。首先,根据JDK和Spring Boot版本选择合适的Flowable版本(7.0以下)。接着,通过创建Spring Boot项目并配置依赖,包括Flowable核心依赖、数据库连接等。然后,建立数据库并配置数据源,确保Flowable能自动生成所需的表结构。最后,启动项目测试,确认Flowable成功创建了79张表。文中还简要介绍了这些表的分类和常用表的作用,帮助初学者理解Flowable的工作原理。
3920 0
SpringBoot整合Flowable【02】- 整合初体验

热门文章

最新文章