
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、問題處理思路打開二級緩存,本地項目使用 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
關注後端面試那些事,回復【2022面經】
獲取最新大廠Java面經

