当前位置: 首页 > 后端技术 > Java

面试官:说说MyBatis二级缓存?协会刷新实施?我傻眼了,.

时间:2023-04-01 20:05:16 Java

来源:blog.csdn.net/qq_38245668/article/details/1058032981,MyBatis缓存介绍Mybatis提供了对缓存的支持,但是默认情况下没有配置,只开启一级缓存,二级缓存缓存需要手动打开。一级缓存只是相对于同一个SqlSession。即对于同一个事务,多次执行同一个Mapper的同一个查询方法。第一次查询后,MyBatis会将查询结果放入缓存中,中间不涉及对应Mapper的数据更新(Insert、Update、Delete)操作。接下来,后续查询将从缓存中获取,而不查询数据库。二级缓存是针对应用级缓存的,也就是针对不同的SqlSession。当启用二级缓存时,MyBatis会将第一个查询结果存储在全局缓存中,供Mapper使用。如果中间没有进行Mapper的数据更新操作,那么后续相同的查询都会从缓存中获取。2.二级缓存问题根据二级缓存的介绍,发现如果Mapper只是单表查询,是不会有问题的,但是如果Mapper中涉及的查询有一个联合表查询,比如UserMapper在查询用户信息时需要关联查询组织信息,即用户表需要关联组织表。OrganizationMapper在执行更新时不会更新UserMapper的缓存。这样一来,在使用UserMapper使用相同条件查询用户信息时,会等待组织信息没有更新,造成数据不一致的情况。2.1.数据不一致问题验证查询SQLSELECTu.*,o.nameorg_nameFROMuseruLEFTJOINorganizationoONu.org_id=o.idWHEREu.id=#{userId}UserMapperUserInfoqueryUserInfo(@Param("userId")StringuserId);UserServicepublicUserEntityqueryUser(StringuserId){UserInfouserInfo=userMapper.queryUserInfo(userId);returnuserInfo;}调用查询得到查询结果(多次查询得到缓存数据),其中userId=1,data为用户查询结果{"code":"1","message":null,"data":{"id":"1","username":"admin","password":"admin","orgName":"Organization1"}}查询对应的组织信息,结果{"code":"1","message":null,"data":{"id":"1","name":"Organization1"}}发现与用户缓存数据一致。执行更新组织操作,将组织1更改为组织2,再次查询组织信息{"code":"1","message":null,"data":{"id":"1","name":"Organization2"}}再次查询用户信息,发现{"code":"1","message":null,"data":{"id":"1","username":"admin"还是obtainedfromcache","password":"admin","orgName":"Organization1"}}出现这个问题的原因是组织数据信息的更新只会更新Mapper对应的缓存数据,而不会通知一些Mapper更新关联表组织对应的缓存数据2.2.问题解决思路在定义Mapper1时,手动配置对应的关联Mapper2,实例化Mapper1缓存cache1时,读取关联Mapper2的cache2相关信息。cache1中cache2的引用信息,当cache1执行clear时,同步操作cache2执行clear3,关联缓存刷新开启二级缓存,本地项目使用MyBatisPlusmybatis-plus.configuration.cache-enabled=true主要使用自定义注解CacheRelations,自定义缓存实现RelativeCache和缓存上下文RelativeCacheContext。注解CacheRelations,使用时需要在对应的mapper上标注@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public@interfaceCacheRelations{//当from中mapper类对应的缓存更新时,缓存当前注解所注解的映射器需要更新Class[]from()default{};//当前注解标记的mapper的缓存更新时,需要更新to中mapper类对应的缓存Class[]to()default{};}自定义缓存RelativeCache实现MyBatisCache接口公共类RelativeCache实现缓存{privateMapCACHE_MAP=newConcurrentHashMap<>();privateListrelations=newArrayList<>();privateReadWriteLockreadWriteLock=newReentrant(Readtrue)WriteLock;私有字符串标识;私有类映射器类;私有布尔清算;publicRelativeCache(Stringid)throwsException{this.id=id;this.mapperClass=Class.forName(id);RelativeCacheContext.putCache(mapperClass,this);加载关系();}@OverridepublicStringgetId(){返回ID;}@OverridepublicvoidputObject(Objectkey,Objectvalue){CACHE_MAP.put(key,value);}@OverridepublicObjectgetObject(Objectkey){returnCACHE_MAP.get(key);}@OverridepublicObjectremoveObject(Objectkey){returnCACHE_MAP.remove(key);}@Overridepublicvoidclear(){ReadWriteLockreadWriteLock=getReadWriteLock();锁定lock=readWriteLock.writeLock();锁.锁();try{//判断当前缓存是否正在清空,如果正在清空,则取消本次操作//避免缓存中出现循环关系,导致无限递归,调用栈溢出if(clearing){return;}清算=真;尝试{CACHE_MAP.clear();relations.forEach(RelativeCache::clear);}最后{clearing=false;}}最后{lock.unlock();}}@OverridepublicintgetSize(){返回CACHE_MAP.size();}@OverridepublicReadWriteLockgetReadWriteLock(){返回readWriteLock;}publicvoidaddRelation(RelativeCacherelation){if(relations.contains(relation)){return;}relations.add(关系);}voidloadRelations(){//加载其他缓存更新时需要更新此缓存的缓存//将此缓存添加到这些缓存的关系中Listto=UN_LOAD_TO_RELATIVE_CACHES_MAP.get(mapperClass);if(to!=null){to.forEach(relativeCache->this.addRelation(relativeCache));}//在这个缓存更新时加载一些需要更新的缓存//将这些缓存添加到这个缓存关系中Listfrom=UN_LOAD_FROM_RELATIVE_CACHES_MAP.get(mapperClass);if(from!=null){from.forEach(relativeCache->relativeCache.addRelation(this));}CacheRelations注释=AnnotationUtils.findAnnotation(mapperClass,CacheRelations.class);如果(注释==null){返回;}Class[]toMappers=annotation.to();类[]fromMappers=annotation.from();if(toMappers!=null&&toMappers.length>0){for(Classc:toMappers){RelativeCacherelativeCache=MAPPER_CACHE_MAP.get(c);if(relativeCache!=null){//将找到的缓存添加到当前缓存的关系中this.addRelation(relativeCache);}else{//如果找不到to缓存,则证明to缓存没有被加载。这时需要在UN_LOAD_FROM_RELATIVE_CACHES_MAP中存入对应关系//即当前缓存更新时需要更新c对应的缓存ListrelativeCaches=UN_LOAD_FROM_RELATIVE_CACHES_MAP.putIfAbsent(c,newArrayList<相对缓存>());relativeCaches.add(this);}}}如果(fromMappers!=null&&fromMappers.llength>0){for(Classc:fromMappers){RelativeCacherelativeCache=MAPPER_CACHE_MAP.get(c);if(relativeCache!=null){//将找到的缓存添加到当前缓存的关系中relativeCache.addRelation(this);}else{//如果找不到from缓存,则证明from缓存没有被加载。这个时候需要把对应关系存入UN_LOAD_TO_RELATIVE_CACHES_MAP//也就是说c对应的缓存更新的时候需要更新当前缓存。ListrelativeCaches=UN_LOAD_TO_RELATIVE_CACHES_MAP.putIfAbsent(c,newArrayList());relativeCaches.add(this);}}}}}缓存上下文RelativeCacheContextpublicclassRelativeCacheContext{//存储全缓存的映射关系publicstaticfinalMap,RelativeCache>MAPPER_CACHE_MAP=newConcurrentHashMap<>();//要存储Mapper对应的缓存需要更新缓存,但是此时Mapper对应的缓存还没有加载//即在更新Class对应的缓存时,需要更新List中的缓存publicstaticfinalMap,List>UN_LOAD_TO_RELATIVE_CACHES_MAP=newConcurrentHashMap<>();//存放Mapper对应的缓存需要从中更新缓存,但是在加载Mapper缓存的时候,这些缓存还没有加载//即List中的缓存更新时,Class对应的缓存需要更新publicstaticfinalMap,List>UN_LOAD_FROM_RELATIVE_CACHES_MAP=newConcurrentHashMap<>();publicstaticvoidputCache(Classclazz,RelativeCachecache){MAPPER_CACHE_MAP.put(clazz,cache);}publicstaticvoidgetCache(Classclazz){MAPPER_CACHE_MAP.get(clazz);}}使用方法UserMapper.java@Repository@CacheNamespace(implementation=RelativeCache.class,eviction=RelativeCache.class,flushInterval=30*60*1000)@CacheRelations(from=OrganizationMapper.class)publicinterfaceUserMapperextendsBaseMapper{用户信息(查询用户信息@Param("userId")StringuserId);}queryUserInfo是xml实现的接口,所以需要在对应的xml中配置,否则查询结果不会缓存如果接口是BaseMapper实现的,查询结果会自动缓存。UserMapper.xmlmars.system.model.UserInfo">选择u.*,o.nameorg_namefromuseruleftjoinorganizationoonu.org_id=o.idwhereu.id=#{userId}OrganizationMapper.java@Repository@CacheNamespace(implementation=RelativeCache.class,eviction=RelativeCache.class,flushInterval=30*60*1000)publicinterfaceOrganizationMapperextendsBaseMapper{}CacheNamespace中的flushInterval默认是无效的,也就是为了比如说,缓存不会定期清理。ScheduledCache是flushInterval函数的实现。MyBatis的缓存系统使用装饰器进行了扩展。因此,如果需要定时刷新,就需要使用ScheduledCache给RelativeCache加装饰。至此,配置和编码完成。开始验证:查询userId=1的用户信息{"code":"1","message":null,"data":{"id":"1","username":"admin","password":"admin","orgName":"Organization1"}}更新组织信息,将组织1更改为组织2{"code":"1","message":null,"data":{"id":"1","name":"Organization2"}}再次查询用户信息{"code":"1","message":null,"data":{"id":"1","username":"admin","password":"admin","orgName":"Organization2"}}正如预期的那样。近期热点文章推荐:1.1000+Java面试题及答案(2022最新版)2.厉害了!Java协程来了。..3.SpringBoot2.x教程,太全面了!4.不要用爆破爆满画面,试试装饰者模式,这才是优雅的方式!!5.《Java开发手册(嵩山版)》最新发布,赶快下载吧!感觉不错,别忘了点赞+转发!