面試官:說(shuō)說(shuō) MyBatis 二級(jí)緩存?關(guān)聯(lián)刷新實(shí)現(xiàn)?我徹底懵B了。。
回復(fù)架構(gòu)師獲取資源
大家好,我是你們的朋友架構(gòu)君,一個(gè)會(huì)寫代碼吟詩(shī)的架構(gòu)師。
1、MyBatis緩存介紹
Mybatis提供對(duì)緩存的支持,但是在沒(méi)有配置的默認(rèn)情況下,它只開啟一級(jí)緩存,二級(jí)緩存需要手動(dòng)開啟。
一級(jí)緩存只是相對(duì)于同一個(gè)SqlSession而言。 也就是針對(duì)于同一事務(wù),多次執(zhí)行同一Mapper的相同查詢方法,第一查詢后,MyBatis會(huì)將查詢結(jié)果放入緩存,在中間不涉及相應(yīng)Mapper的數(shù)據(jù)更新(Insert,Update和Delete)操作的情況下,后續(xù)的查詢將會(huì)從緩存中獲取,而不會(huì)查詢數(shù)據(jù)庫(kù)。
二級(jí)緩存是針對(duì)于應(yīng)用級(jí)別的緩存,也就是針對(duì)不同的SqlSession做到緩存。 當(dāng)開啟二級(jí)緩存時(shí),MyBatis會(huì)將首次查詢結(jié)果存入對(duì)于Mapper的全局緩存,如果中間不執(zhí)行該Mapper的數(shù)據(jù)更新操作,那么后續(xù)的相同查詢都將會(huì)從緩存中獲取。
2、二級(jí)緩存問(wèn)題
根據(jù)二級(jí)緩存的介紹發(fā)現(xiàn),如果Mapper只是單表查詢,并不會(huì)出現(xiàn)問(wèn)題,但是如果Mapper涉及的查詢出現(xiàn) 聯(lián)表 查詢,如 UserMapper 在查詢 user 信息時(shí)需要關(guān)聯(lián)查詢 組織信息,也就是需要 user 表和 organization 表關(guān)聯(lián),OrganizationMapper 在執(zhí)行更新時(shí)并不會(huì)更新 UserMapper 的緩存,結(jié)果會(huì)導(dǎo)致在使用相同條件 使用 UserMapper 查詢 user 信息時(shí),會(huì)等到未更新前的 organization 信息,造成數(shù)據(jù)不一致的情況。
2.1、數(shù)據(jù)不一致問(wèn)題驗(yàn)證
查詢SQL
SELECT
?u.*,?o.name?org_name?
FROM
?user?u
?LEFT?JOIN?organization?o?ON?u.org_id?=?o.id?
WHERE
?u.id?=?#{userId}
UserMapper
UserInfo?queryUserInfo(@Param("userId")?String?userId);
UserService
public?UserEntity?queryUser(String?userId)?{
????UserInfo?userInfo?=?userMapper.queryUserInfo(userId);
????return?userInfo;
}
調(diào)用查詢,得到查詢結(jié)果(多次查詢,得到緩存數(shù)據(jù)),這里 userId = 1,data為user查詢結(jié)果
{
?"code":?"1",
?"message":?null,
?"data":?{
???"id":?"1",
???"username":?"admin",
???"password":?"admin",
???"orgName":?"組織1"
?}
}
查詢 對(duì)應(yīng) organization 信息,結(jié)果
{
?"code":?"1",
?"message":?null,
?"data":?{
???"id":?"1",
???"name":?"組織1"
?}
}
發(fā)現(xiàn)和user緩存數(shù)據(jù)一致。
執(zhí)行更新 organization 操作,將 組織1 改為 組織2,再次查詢組織信息
{
?"code":?"1",
?"message":?null,
?"data":?{
???"id":?"1",
???"name":?"組織2"
?}
}
再次查詢user信息,發(fā)現(xiàn)依舊從緩存中獲取
{
?"code":?"1",
?"message":?null,
?"data":?{
???"id":?"1",
???"username":?"admin",
???"password":?"admin",
???"orgName":?"組織1"
?}
}
造成此問(wèn)題原因?yàn)?organization 數(shù)據(jù)信息更新只會(huì)自己Mapper對(duì)應(yīng)的緩存數(shù)據(jù),而不會(huì)通知到關(guān)聯(lián)表organization 的一些Mapper更新對(duì)應(yīng)的緩存數(shù)據(jù)。
2.2、問(wèn)題處理思路
在 Mapper1 定義時(shí),手動(dòng)配置 相應(yīng)的關(guān)聯(lián) Mapper2 在 Mapper1 緩存 cache1 實(shí)例化時(shí),讀取 所關(guān)聯(lián)的 Mapper2 的緩存 cache2相關(guān)信息 在 cache1 中存儲(chǔ) cache2 的引用信息 cache1 執(zhí)行clear時(shí),同步操作 cache2 執(zhí)行clear
3、關(guān)聯(lián)緩存刷新實(shí)現(xiàn)
打開二級(jí)緩存,本地項(xiàng)目使用 MyBatis Plus
mybatis-plus.configuration.cache-enabled=true
主要用到自定義注解CacheRelations,自定義緩存實(shí)現(xiàn)RelativeCache和緩存上下文RelativeCacheContext。最新 MyBatis?面試題整理好了,點(diǎn)擊Java面試庫(kù)小程序在線刷題。
注解CacheRelations,使用時(shí)需標(biāo)注在對(duì)應(yīng)mapper上
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public?@interface?CacheRelations?{
????//?from中mapper?class對(duì)應(yīng)的緩存更新時(shí),需要更新當(dāng)前注解標(biāo)注mapper的緩存
????Class>[]?from()?default?{};
????//?當(dāng)前注解標(biāo)注mapper的緩存更新時(shí),需要更新to中mapper?class對(duì)應(yīng)的緩存
????Class>[]?to()?default?{};
}
自定義緩存RelativeCache實(shí)現(xiàn) MyBatis Cache 接口
public?class?RelativeCache?implements?Cache?{
????private?Map緩存上下文RelativeCacheContext
public?class?RelativeCacheContext?{
????//?存儲(chǔ)全量緩存的映射關(guān)系
????public?static?final?Map,?RelativeCache>?MAPPER_CACHE_MAP?=?new?ConcurrentHashMap<>();
????//?存儲(chǔ)?Mapper?對(duì)應(yīng)緩存?需要to更新緩存,但是此時(shí)?Mapper?對(duì)應(yīng)緩存還未加載
????//?也就是?Class>?對(duì)應(yīng)的緩存更新時(shí),需要更新?List?中的緩存
????public?static?final?Map,?List>?UN_LOAD_TO_RELATIVE_CACHES_MAP?=?new?ConcurrentHashMap<>();
????//?存儲(chǔ)?Mapper?對(duì)應(yīng)緩存?需要from更新緩存,但是在?加載?Mapper?緩存時(shí),這些緩存還未加載
????//?也就是?List?中的緩存更新時(shí),需要更新?Class>?對(duì)應(yīng)的緩存
????public?static?final?Map,?List>?UN_LOAD_FROM_RELATIVE_CACHES_MAP?=?new?ConcurrentHashMap<>();
????public?static?void?putCache(Class>?clazz,?RelativeCache?cache)?{
????????MAPPER_CACHE_MAP.put(clazz,?cache);
????}
????public?static?void?getCache(Class>?clazz)?{
????????MAPPER_CACHE_MAP.get(clazz);
????}
}
使用方式
UserMapper.java
@Repository
@CacheNamespace(implementation?=?RelativeCache.class,?eviction?=?RelativeCache.class,?flushInterval?=?30?*?60?*?1000)
@CacheRelations(from?=?OrganizationMapper.class)
public?interface?UserMapper?extends?BaseMapper?{
????UserInfo?queryUserInfo(@Param("userId")?String?userId);
}
queryUserInfo是xml實(shí)現(xiàn)的接口,所以需要在對(duì)應(yīng)xml中配置,不然查詢結(jié)果不會(huì)被緩存化。如果接口為 BaseMapper實(shí)現(xiàn),查詢結(jié)果會(huì)自動(dòng)緩存化。
UserMapper.xml
"com.mars.system.dao.UserMapper">
????"com.mars.system.dao.UserMapper"/>
????
OrganizationMapper.java
@Repository
@CacheNamespace(implementation?=?RelativeCache.class,?eviction?=?RelativeCache.class,?flushInterval?=?30?*?60?*?1000)
public?interface?OrganizationMapper?extends?BaseMapper?{
}
CacheNamespace中flushInterval 在默認(rèn)情況下是無(wú)效的,也就是說(shuō)緩存并不會(huì)定時(shí)清理。ScheduledCache是對(duì)flushInterval 功能的實(shí)現(xiàn),MyBatis 的緩存體系是用裝飾器進(jìn)行功能擴(kuò)展的,所以,如果需要定時(shí)刷新,需要使用ScheduledCache給到 RelativeCache添加裝飾。
開始驗(yàn)證:
查詢 userId=1的用戶信息
{
????"code":"1",
????"message":null,
????"data":{
????????"id":"1",
????????"username":"admin",
????????"password":"admin",
????????"orgName":"組織1"
????}
}
更新組織信息,將 組織1 改為 組織2
{
????"code":"1",
????"message":null,
????"data":{
????????"id":"1",
????????"name":"組織2"
????}
}
再次查詢用戶信息
{
????"code":"1",
????"message":null,
????"data":{
????????"id":"1",
????????"username":"admin",
????????"password":"admin",
????????"orgName":"組織2"
????}
}
符合預(yù)期。
文章來(lái)源:http://blog.csdn.net/qq_38245668/article/details/105803298
這些年小編給你分享過(guò)的干貨
2.優(yōu)質(zhì)ERP系統(tǒng)帶進(jìn)銷存財(cái)務(wù)生產(chǎn)功能(附源碼)
3.優(yōu)質(zhì)SpringBoot帶工作流管理項(xiàng)目(附源碼)
4.最好用的OA系統(tǒng),拿來(lái)即用(附源碼)
5.SBoot+Vue外賣系統(tǒng)前后端都有(附源碼)
6.SBoot+Vue可視化大屏拖拽項(xiàng)目(附源碼)

轉(zhuǎn)發(fā)在看就是最大的支持??
