数据库与缓存不一致的问题:JPA实体状态
前言
公司“818情人节大促”这天,我刚刚坐到工位准备对早餐发起攻势的时候,我的老大发了几个图片,然后说“小李,赶紧看看这个问题,这个商品明明在营销活动中删除了,为什么在商品列表和详情的时候还显示营销标签呢?”我立刻放下早餐和手机,打开电脑,开始排查问题。
过程
首先,我想了一下,为了顺利顶住818大促各种营销活动的QPS,也为了使平台的营销活动更加灵活,我们前段时间对整个营销相关的活动进行了种种改造,其中有一项改造就是把所有与营销活动相关的查询都存储到缓存中。
所以我基于这个点去考虑,管理端的营销活动删除了该商品,但移动端仍然显示,那应该是数据库中该营销活动已经删除了该商品,缓存中没有删除成功。
我首先根据这个营销ID,在数据库确认下,商品是否还关联,一看表中已经不关联了;然后我在缓存确认下,果然发现缓存中确实仍存在。开始翻阅代码,代码如下。
代码示例
营销服务类
营销类

营销关联的商品类

营销数据交互类

关联商品数据交互类

仔仔细细翻阅了代码,发现业务逻辑如下:
查询营销活动通过营销id
各种填充新数据
根据营销id删除关联商品
保存营销活动
批量生成关联商品集合
批量保存关联商品
整理出需要初始化的商品id集合
初始化缓存
按理说这个逻辑是OK的,但数据库和缓存就是不一致,开始分析。
分析
在初始化缓存时是不是没有删除旧的缓存数据?
我们首先想到的是在initCache方法的时候,是不是没有删除旧的缓存数据,但点开这个方法发现调用的删除缓存数据方法,在其他地方都在用,而且近期也没有对这个方法进行改造,说明是OK的。
初始化缓存时查询的关联商品数据是上面第4步之前的数据?
既然删除没有问题,我们就考虑是不是初始化缓存,在查询营销缓存的时候,拿到的是删除前的数据,以至于数据库和缓存不一致?
我们在初始化缓存的方法中找到查询数据库语句,加上日志,果然在日志中发现,查询的是删除前的数据。
百思不得其解,这几个步骤都在同一个事务中,而且初始化缓存是等事务成功之后才做的初始化,那为什么还查询的是旧数据?
又仔仔细细地翻阅了代码,发现答案就在“Marketing”这个类中

主人公登场,这个@OneToMany的锅,为什么出现这个问题呢?这归根结底还是JPA entity生命周期的状态导致的,简称实体状态。
JPA 实体状态
由于我的marketing对象与marketingGoods为一对多的关系,我在删除marketingGoods时,marketing已经查询出来(未查询不在此列),当
我删除marketingGoods时,JPA将marketingGoods设置为删除状态,但是marketing也持有marketingGoods,并且marketing里的marketingGoods为持久状态,所以导致marketingGoods不能删除掉,进而导致初始化缓存查询的还是旧数据。
解决方案
在保存营销活动时,先把关联商品集合设置为清除状态。

再次执行了一遍程序,发现问题解决了,我长长地呼出一口气。
写在最后
好兄弟可以点赞并关注我的公众号“javaAnswer”,全部都是干货。
