@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();
}

小结

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

目录
相关文章
|
9月前
|
XML Java 关系型数据库
@Transactional注解的失效场景
@Transactional注解的失效场景
139 1
|
9月前
|
Java 编译器 数据库
在事务注解@Transactional中指定rollbackFor
在事务注解@Transactional中指定rollbackFor
77 0
|
4月前
|
监控 Java 数据库
Spring事务中的@Transactional注解剖析
通过上述分析,可以看到 `@Transactional`注解在Spring框架中扮演着关键角色,它简化了事务管理的复杂度,让开发者能够更加专注于业务逻辑本身。合理运用并理解其背后的机制,对于构建稳定、高效的Java企业应用至关重要。
108 0
|
8月前
|
SQL Java 数据库
Transactional注解讲解及使用
事务是数据库操作的一组集合,它作为一个工作单元,要求所有操作要么全部成功,要么全部失败。事务的四个基本特性是原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。
|
9月前
|
Java 编译器 Spring
@transactional注解失效情况
@transactional注解失效情况
|
9月前
|
关系型数据库 Java MySQL
一篇文章学会使用@Transactional
一篇文章学会使用@Transactional
101 0
|
SQL Java 数据库连接
@Transactional
@Transactional
122 0
|
Java 数据库 Spring
@Transactional注解超详细
@Transactional注解超详细
1075 0
|
Java 数据库 Spring
@Transactional 注解失效问题
@Transactional 注解失效问题
127 0

热门文章

最新文章