JPA-update方法使用踩坑记

JPA踩坑问题

最近项目中使用了JPA,在使用JPA的时候,由于疏忽,出现了一个问题,还是因为对JPA不熟悉导致的,问题是这样的:

先总结说一下问题的原因:
在A方法中,对数据库的订单表(order)的date字段进行更新,但是在事务还没有提交的时候,收到了mq消息,会更新order表的状态字段status,但是由于A方法还没有提交事务,所以,mq更新状态字段的方法会覆盖A方法中的date更新信息,将date覆盖为A方法更新之前的值

在这里插入图片描述
这里我解释下时序图的意思:

  1. A系统是我这里负责的系统,B系统是我的下游,在客户预约时间之后,我首先会把客户的预约时间存到数据库的订单表里面,这是时序图中第一步做的事情
  2. 第二步:会把这个时间同步给下游,这里就是一个简单的dubbo接口的调用
  3. 第三步,我这里会接着处理其他的一些逻辑,但是有一个问题:1、2、3这三步,是在同一个事务里面,这样的话,只有在1、2、3全部完成的时候,才会提交事务,把时间更新到数据库的订单表里面
  4. 但是在第2步,调用了下游接口之后,下游会发一个mq消息回来,通知我的A系统,进行一个状态的变更,也就是updateStatus方法,这里的状态并不是我自己订单的状态,而是一个下游的业务状态(X字段),但是也是在订单表中,所以收到第4步的消息之后,我会根据订单id查询订单表
  5. 第5步就是更新X字段的值;问题就出现在这里:第五步的时候,我为了图方便,就调用了JPA的
org.springframework.data.repository.CrudRepository#save

这个save方法就是记录不存在,就insert,记录存在,就执行update;

那么问题出现在哪呢?

  1. updateDate这个方法事务还未提交的时候 ,就收到了下游的mq消息
  2. 收到消息的时候,根据主键ID查询数据库,获取到订单记录,此时订单记录的时间字段还是null,因为updateDate事务还没有提交
  3. 然后在第5步进行逻辑处理的时候,updateDate进行了事务提交,但是在事务提交之前,updateStatus方法已经查询了数据库,获取到的date是null,所以,updateStatus在更新status的时候,直接把date字段更新为了null

所以,问题点在于:
1.在A方法的事务还没有提交的时候,B方法从数据库中查询到了数据,但是是A事务提交之前的
2.B方法使用的是更新所有字段,在B提交事务之前,A事务进行了提交
3.待B方法更新的时候,会把A事务更新的字段覆盖

解决办法

当时由于急着上线发布,所以就先暂时把updateStatus里面调用的jpa的save方法,换成了本地sql来写,就是

@Query(value = "update order  set status = :status where id = :id",nativeQuery = true)
@Modifying

通过本地sql,只更新自己在本方法中,需要更新的字段

第二种办法:
我后来考虑了下,可以考虑在updateDate方法中,将事务的范围缩小,在1和2执行之后,就提交事务,至于第三步的其他一些逻辑,如果出错了,就只把第三步的进行回退,在客户再次预约的时候,会把上一次预约失败的时间进行覆盖
并且大事务方法的使用,还是有一定的风险的

总结

总结而言,就是一句话:对于不熟悉的技术,还是要谨慎,如果不了解一个框架的特性,那就尽量自己去控制方法的执行,这样,至少可以保证系统的稳定,在熟悉之后,再对业务逻辑和代码进行重构

猜你喜欢

转载自blog.csdn.net/CPLASF_/article/details/108035943