close
關注我,回復關鍵字「spring」,
免費領取Spring學習資料。

1、MyBatis緩存介紹

Mybatis提供對緩存的支持,但是在沒有配置的默認情況下,它只開啟一級緩存,二級緩存需要手動開啟。

一級緩存只是相對於同一個SqlSession而言。也就是針對於同一事務,多次執行同一Mapper的相同查詢方法,第一查詢後,MyBatis會將查詢結果放入緩存,在中間不涉及相應Mapper的數據更新(Insert,Update和Delete)操作的情況下,後續的查詢將會從緩存中獲取,而不會查詢數據庫。

二級緩存是針對於應用級別的緩存,也就是針對不同的SqlSession做到緩存。當開啟二級緩存時,MyBatis會將首次查詢結果存入對於Mapper的全局緩存,如果中間不執行該Mapper的數據更新操作,那麼後續的相同查詢都將會從緩存中獲取。

2、二級緩存問題

根據二級緩存的介紹發現,如果Mapper只是單表查詢,並不會出現問題,但是如果Mapper涉及的查詢出現 聯表 查詢,如 UserMapper 在查詢 user 信息時需要關聯查詢 組織信息,也就是需要 user 表和 organization 表關聯,OrganizationMapper 在執行更新時並不會更新 UserMapper 的緩存,結果會導致在使用相同條件 使用 UserMapper 查詢 user 信息時,會等到未更新前的 organization 信息,造成數據不一致的情況。

2.1、數據不一致問題驗證

查詢SQL

SELECTu.*,o.nameorg_nameFROMuseruLEFTJOINorganizationoONu.org_id=o.idWHEREu.id=#{userId}

UserMapper

UserInfoqueryUserInfo(@Param("userId")StringuserId);

UserService

publicUserEntityqueryUser(StringuserId){UserInfouserInfo=userMapper.queryUserInfo(userId);returnuserInfo;}

調用查詢,得到查詢結果(多次查詢,得到緩存數據),這裡userId = 1,data為user查詢結果

{"code":"1","message":null,"data":{"id":"1","username":"admin","password":"admin","orgName":"組織1"}}

查詢 對應 organization 信息,結果

{"code":"1","message":null,"data":{"id":"1","name":"組織1"}}

發現和user緩存數據一致。

執行更新 organization 操作,將 組織1 改為 組織2,再次查詢組織信息

{"code":"1","message":null,"data":{"id":"1","name":"組織2"}}

再次查詢user信息,發現依舊從緩存中獲取

{"code":"1","message":null,"data":{"id":"1","username":"admin","password":"admin","orgName":"組織1"}}

造成此問題原因為 organization 數據信息更新只會自己Mapper對應的緩存數據,而不會通知到關聯表organization 的一些Mapper更新對應的緩存數據。

2.2、問題處理思路
在 Mapper1 定義時,手動配置 相應的關聯 Mapper2
在 Mapper1 緩存 cache1 實例化時,讀取 所關聯的 Mapper2 的緩存 cache2相關信息
在 cache1 中存儲 cache2 的引用信息
cache1 執行clear時,同步操作 cache2 執行clear
3、關聯緩存刷新實現

打開二級緩存,本地項目使用 MyBatis Plus

mybatis-plus.configuration.cache-enabled=true

主要用到自定義註解CacheRelations,自定義緩存實現RelativeCache和緩存上下文RelativeCacheContext。

註解CacheRelations,使用時需標註在對應mapper上

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public@interfaceCacheRelations{//from中mapperclass對應的緩存更新時,需要更新當前註解標註mapper的緩存Class<?>[]from()default{};//當前註解標註mapper的緩存更新時,需要更新to中mapperclass對應的緩存Class<?>[]to()default{};}

自定義緩存RelativeCache實現 MyBatis Cache 接口

publicclassRelativeCacheimplementsCache{privateMap<Object,Object>CACHE_MAP=newConcurrentHashMap<>();privateList<RelativeCache>relations=newArrayList<>();privateReadWriteLockreadWriteLock=newReentrantReadWriteLock(true);privateStringid;privateClass<?>mapperClass;privatebooleanclearing;publicRelativeCache(Stringid)throwsException{this.id=id;this.mapperClass=Class.forName(id);RelativeCacheContext.putCache(mapperClass,this);loadRelations();}@OverridepublicStringgetId(){returnid;}@OverridepublicvoidputObject(Objectkey,Objectvalue){CACHE_MAP.put(key,value);}@OverridepublicObjectgetObject(Objectkey){returnCACHE_MAP.get(key);}@OverridepublicObjectremoveObject(Objectkey){returnCACHE_MAP.remove(key);}@Overridepublicvoidclear(){ReadWriteLockreadWriteLock=getReadWriteLock();Locklock=readWriteLock.writeLock();lock.lock();try{//判斷當前緩存是否正在清空,如果正在清空,取消本次操作//避免緩存出現循環relation,造成遞歸無終止,調用棧溢出if(clearing){return;}clearing=true;try{CACHE_MAP.clear();relations.forEach(RelativeCache::clear);}finally{clearing=false;}}finally{lock.unlock();}}@OverridepublicintgetSize(){returnCACHE_MAP.size();}@OverridepublicReadWriteLockgetReadWriteLock(){returnreadWriteLock;}publicvoidaddRelation(RelativeCacherelation){if(relations.contains(relation)){return;}relations.add(relation);}voidloadRelations(){//加載其他緩存更新時需要更新此緩存的caches//將此緩存加入至這些caches的relations中List<RelativeCache>to=UN_LOAD_TO_RELATIVE_CACHES_MAP.get(mapperClass);if(to!=null){to.forEach(relativeCache->this.addRelation(relativeCache));}//加載此緩存更新時需要更新的一些緩存caches//將這些緩存caches加入至此緩存relations中List<RelativeCache>from=UN_LOAD_FROM_RELATIVE_CACHES_MAP.get(mapperClass);if(from!=null){from.forEach(relativeCache->relativeCache.addRelation(this));}CacheRelationsannotation=AnnotationUtils.findAnnotation(mapperClass,CacheRelations.class);if(annotation==null){return;}Class<?>[]toMappers=annotation.to();Class<?>[]fromMappers=annotation.from();if(toMappers!=null&&toMappers.length>0){for(Classc:toMappers){RelativeCacherelativeCache=MAPPER_CACHE_MAP.get(c);if(relativeCache!=null){//將找到的緩存添加到當前緩存的relations中this.addRelation(relativeCache);}else{//如果找不到tocache,證明tocache還未加載,這時需將對應關係存放到UN_LOAD_FROM_RELATIVE_CACHES_MAP//也就是說c對應的cache需要在當前緩存更新時進行更新List<RelativeCache>relativeCaches=UN_LOAD_FROM_RELATIVE_CACHES_MAP.putIfAbsent(c,newArrayList<RelativeCache>());relativeCaches.add(this);}}}if(fromMappers!=null&&fromMappers.length>0){for(Classc:fromMappers){RelativeCacherelativeCache=MAPPER_CACHE_MAP.get(c);if(relativeCache!=null){//將找到的緩存添加到當前緩存的relations中relativeCache.addRelation(this);}else{//如果找不到fromcache,證明fromcache還未加載,這時需將對應關係存放到UN_LOAD_TO_RELATIVE_CACHES_MAP//也就是說c對應的cache更新時需要更新當前緩存List<RelativeCache>relativeCaches=UN_LOAD_TO_RELATIVE_CACHES_MAP.putIfAbsent(c,newArrayList<RelativeCache>());relativeCaches.add(this);}}}}}

緩存上下文RelativeCacheContext

publicclassRelativeCacheContext{//存儲全量緩存的映射關係publicstaticfinalMap<Class<?>,RelativeCache>MAPPER_CACHE_MAP=newConcurrentHashMap<>();//存儲Mapper對應緩存需要to更新緩存,但是此時Mapper對應緩存還未加載//也就是Class<?>對應的緩存更新時,需要更新List<RelativeCache>中的緩存publicstaticfinalMap<Class<?>,List<RelativeCache>>UN_LOAD_TO_RELATIVE_CACHES_MAP=newConcurrentHashMap<>();//存儲Mapper對應緩存需要from更新緩存,但是在加載Mapper緩存時,這些緩存還未加載//也就是List<RelativeCache>中的緩存更新時,需要更新Class<?>對應的緩存publicstaticfinalMap<Class<?>,List<RelativeCache>>UN_LOAD_FROM_RELATIVE_CACHES_MAP=newConcurrentHashMap<>();publicstaticvoidputCache(Class<?>clazz,RelativeCachecache){MAPPER_CACHE_MAP.put(clazz,cache);}publicstaticvoidgetCache(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)publicinterfaceUserMapperextendsBaseMapper<UserEntity>{UserInfoqueryUserInfo(@Param("userId")StringuserId);}

queryUserInfo是xml實現的接口,所以需要在對應xml中配置<cache-ref namespace=「com.mars.system.dao.UserMapper」/>,不然查詢結果不會被緩存化。如果接口為 BaseMapper實現,查詢結果會自動緩存化。

UserMapper.xml

<mappernamespace="com.mars.system.dao.UserMapper"><cache-refnamespace="com.mars.system.dao.UserMapper"/><selectid="queryUserInfo"resultType="com.mars.system.model.UserInfo">selectu.*,o.nameorg_namefromuseruleftjoinorganizationoonu.org_id=o.idwhereu.id=#{userId}</select></mapper>

OrganizationMapper.java

@Repository@CacheNamespace(implementation=RelativeCache.class,eviction=RelativeCache.class,flushInterval=30*60*1000)publicinterfaceOrganizationMapperextendsBaseMapper<OrganizationEntity>{}

CacheNamespace中flushInterval 在默認情況下是無效的,也就是說緩存並不會定時清理。ScheduledCache是對flushInterval 功能的實現,MyBatis 的緩存體系是用裝飾器進行功能擴展的,所以,如果需要定時刷新,需要使用ScheduledCache給到 RelativeCache添加裝飾。

至此,配置和編碼完成。

開始驗證:

查詢 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"}}

符合預期。

來源:blog.csdn.net/qq_38245668/article/details/105803298


END



面試被問到了String相關的幾道題,你能答上來嗎?
SpringBoot + Elasticsearch 實現簡單查詢及高亮分詞查詢
Spring Cloud Alibaba基礎教程:使用Nacos作為配置中心
MySQL模糊查詢再也用不着 like+% 了!
Spring Event,賊好用的業務解耦神器!

關注後端面試那些事,回復【2022面經】

獲取最新大廠Java面經


最後重要提示:高質量的技術交流群,限時免費開放,今年抱團最重要。想進群的,關注SpringForAll社區,回復關鍵詞:加群,拉你進群。




點擊「閱讀原文」領取2022大廠面經
↓↓↓
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()