@Transactional注解的一个很容易被忽略的错误用法

@Transactional注解的一个很容易被忽略的错误用法

今日审查代码时发现对@Transactional注解的一个如下用法:

less 复制代码
//StockController.java

@AutoWired
private StockService stockService

//商品出库
@PostMapping("/product/stock/out-stock")
public Boolean productOutStock(@RequestBody StockChangeDTO data) {
  return stockService.outStock(data);
}

//StockService服务
public Boolean outStock(StockChangeDTO data) {
  //此处省略相关数据检查的前置代码
  ..........
  
  //执行出库的逻辑,acOut表示出库操作
  return this.doProductStockChange(data, acOut)
}

//执行出库的逻辑
@Transactional(rollback = Exception.class)
public Boolean doProductStockChange(StockChangeDTO data, StockChangeAction action) {
  //记录出入库记录
  
  //改变商品库存
  
  //省略其他逻辑代码
  .............
}

这段代码的逻辑是在StockService.outStock方法中调用了一个公用方法doProductStockChange完成出库逻辑,在doProductStockChange方法中涉及多笔数据操作,有记录出入库记录,有更改商品库存数据等等,为保证数据的一致性,这里添加了事物注解@Transactional来保证数据的一致性,乍一看好像没什么问题。

但是仔细一思考,这么用真的能保证事物能生效吗?

了解@Transactional注解的原理,当我们在方法上使用 @Transactional 注解时,Spring 将会创建一个代理对象来包装该方法。该代理对象将在该方法执行之前和之后添加一些代码,以启动和提交/回滚事务。如下图所示:

也就是说我们在StockController中注入StockService的Bean时,如果StockService中没有用到@Transactional之类的注解,那么注入的就是StockService服务本身的bean,相反如果有这类的注解时,那么注入的就不再是StockService本身的bean了,而是经过包装了一层的代理对象,这个代码对象在方法调用前会启动事物,在方法调用后提交事物,发生异常时执行事物回滚,这样就能保证事物是生效的。

我们再来看看StockService.java中outStock方法调用this.doProductStockChange方法,虽然doProductStockChange方法添加了事物注解,但是由于这个时候的this对象就是StockService本身而不是代理对象,因此事物就是无效的,这段代码并不能保证出入库时的数据一致性。

解决办法有以下几种:

1、在StockService.java中注入自己,然后调用doProductStockChange方法改为调用注入对象的;

kotlin 复制代码
//StockService.java

@AutoWired
private StockService stockService

public Boolean outStock(StockChangeDTO data) {
  //此处省略相关数据检查的前置代码
  ..........
  
  //执行出库的逻辑,acOut表示出库操作
  return stockService.doProductStockChange(data, acOut)
}

2、将outStock方法中的数据检查代码前置到controller中去检查,然后直接调用stockService.doProductStockChange方法。

第一种方法属于为了临时快速解决问题的偏门方法,对代码的可阅读性造成伤害,个人建议还是第二种方法更好更值得推荐使用。 仔细检查下代码中是否还有其他注解类似的错误,比如@ASync, @Cacheable、@CachePut、@CacheEvict, @Scheduled等等。

相关推荐
流浪克拉玛依1 天前
Go Web 服务限流器实战:从原理到压测验证 --使用 Gin 框架 + Uber Ratelimit / 官方限流器,并通过 Vegeta 进行性能剖析
后端
孟沐1 天前
保姆级教程:手写三层架构 vs MyBatis-Plus
后端
星浩AI1 天前
让模型自己写 Skills——从素材到自动生成工作流
人工智能·后端·agent
华仔啊1 天前
为啥不用 MP 的 saveOrUpdateBatch?MySQL 一条 SQL 批量增改才是最优解
java·后端
武子康1 天前
大数据-242 离线数仓 - DataX 实战:MySQL 全量/增量导入 HDFS + Hive 分区(离线数仓 ODS
大数据·后端·apache hive
砍材农夫1 天前
TCP和UDP区别
后端
千寻girling1 天前
一份不可多得的 《 Django 》 零基础入门教程
后端·python·面试
千寻girling1 天前
Python 是用来做 AI 人工智能 的 , 不适合开发 Web 网站 | 《Web框架》
人工智能·后端·算法
贾铭1 天前
如何实现一个网页版的剪映(三)使用fabric.js绘制时间轴
前端·后端
xiaoye20181 天前
Spring 自定义 Redis 超时:TTL、TTI 与 Pipeline 实战
后端