數(shù)據(jù)庫(kù)與緩存不一致的問(wèn)題:JPA實(shí)體狀態(tài)
前言
公司“818情人節(jié)大促”這天,我剛剛坐到工位準(zhǔn)備對(duì)早餐發(fā)起攻勢(shì)的時(shí)候,我的老大發(fā)了幾個(gè)圖片,然后說(shuō)“小李,趕緊看看這個(gè)問(wèn)題,這個(gè)商品明明在營(yíng)銷活動(dòng)中刪除了,為什么在商品列表和詳情的時(shí)候還顯示營(yíng)銷標(biāo)簽?zāi)??”我立刻放下早餐和手機(jī),打開(kāi)電腦,開(kāi)始排查問(wèn)題。
過(guò)程
首先,我想了一下,為了順利頂住818大促各種營(yíng)銷活動(dòng)的QPS,也為了使平臺(tái)的營(yíng)銷活動(dòng)更加靈活,我們前段時(shí)間對(duì)整個(gè)營(yíng)銷相關(guān)的活動(dòng)進(jìn)行了種種改造,其中有一項(xiàng)改造就是把所有與營(yíng)銷活動(dòng)相關(guān)的查詢都存儲(chǔ)到緩存中。
所以我基于這個(gè)點(diǎn)去考慮,管理端的營(yíng)銷活動(dòng)刪除了該商品,但移動(dòng)端仍然顯示,那應(yīng)該是數(shù)據(jù)庫(kù)中該營(yíng)銷活動(dòng)已經(jīng)刪除了該商品,緩存中沒(méi)有刪除成功。
我首先根據(jù)這個(gè)營(yíng)銷ID,在數(shù)據(jù)庫(kù)確認(rèn)下,商品是否還關(guān)聯(lián),一看表中已經(jīng)不關(guān)聯(lián)了;然后我在緩存確認(rèn)下,果然發(fā)現(xiàn)緩存中確實(shí)仍存在。開(kāi)始翻閱代碼,代碼如下。
代碼示例
營(yíng)銷服務(wù)類
營(yíng)銷類

營(yíng)銷關(guān)聯(lián)的商品類

營(yíng)銷數(shù)據(jù)交互類

關(guān)聯(lián)商品數(shù)據(jù)交互類

仔仔細(xì)細(xì)翻閱了代碼,發(fā)現(xiàn)業(yè)務(wù)邏輯如下:
查詢營(yíng)銷活動(dòng)通過(guò)營(yíng)銷id
各種填充新數(shù)據(jù)
根據(jù)營(yíng)銷id刪除關(guān)聯(lián)商品
保存營(yíng)銷活動(dòng)
批量生成關(guān)聯(lián)商品集合
批量保存關(guān)聯(lián)商品
整理出需要初始化的商品id集合
初始化緩存
按理說(shuō)這個(gè)邏輯是OK的,但數(shù)據(jù)庫(kù)和緩存就是不一致,開(kāi)始分析。
分析
在初始化緩存時(shí)是不是沒(méi)有刪除舊的緩存數(shù)據(jù)?
我們首先想到的是在initCache方法的時(shí)候,是不是沒(méi)有刪除舊的緩存數(shù)據(jù),但點(diǎn)開(kāi)這個(gè)方法發(fā)現(xiàn)調(diào)用的刪除緩存數(shù)據(jù)方法,在其他地方都在用,而且近期也沒(méi)有對(duì)這個(gè)方法進(jìn)行改造,說(shuō)明是OK的。
初始化緩存時(shí)查詢的關(guān)聯(lián)商品數(shù)據(jù)是上面第4步之前的數(shù)據(jù)?
既然刪除沒(méi)有問(wèn)題,我們就考慮是不是初始化緩存,在查詢營(yíng)銷緩存的時(shí)候,拿到的是刪除前的數(shù)據(jù),以至于數(shù)據(jù)庫(kù)和緩存不一致?
我們?cè)诔跏蓟彺娴姆椒ㄖ姓业讲樵償?shù)據(jù)庫(kù)語(yǔ)句,加上日志,果然在日志中發(fā)現(xiàn),查詢的是刪除前的數(shù)據(jù)。
百思不得其解,這幾個(gè)步驟都在同一個(gè)事務(wù)中,而且初始化緩存是等事務(wù)成功之后才做的初始化,那為什么還查詢的是舊數(shù)據(jù)?
又仔仔細(xì)細(xì)地翻閱了代碼,發(fā)現(xiàn)答案就在“Marketing”這個(gè)類中

主人公登場(chǎng),這個(gè)@OneToMany的鍋,為什么出現(xiàn)這個(gè)問(wèn)題呢?這歸根結(jié)底還是JPA entity生命周期的狀態(tài)導(dǎo)致的,簡(jiǎn)稱實(shí)體狀態(tài)。
JPA 實(shí)體狀態(tài)
由于我的marketing對(duì)象與marketingGoods為一對(duì)多的關(guān)系,我在刪除marketingGoods時(shí),marketing已經(jīng)查詢出來(lái)(未查詢不在此列),當(dāng)
我刪除marketingGoods時(shí),JPA將marketingGoods設(shè)置為刪除狀態(tài),但是marketing也持有marketingGoods,并且marketing里的marketingGoods為持久狀態(tài),所以導(dǎo)致marketingGoods不能刪除掉,進(jìn)而導(dǎo)致初始化緩存查詢的還是舊數(shù)據(jù)。
解決方案
在保存營(yíng)銷活動(dòng)時(shí),先把關(guān)聯(lián)商品集合設(shè)置為清除狀態(tài)。

再次執(zhí)行了一遍程序,發(fā)現(xiàn)問(wèn)題解決了,我長(zhǎng)長(zhǎng)地呼出一口氣。
寫(xiě)在最后
好兄弟可以點(diǎn)贊并關(guān)注我的公眾號(hào)“javaAnswer”,全部都是干貨。
