MyBatis整合Redis实现二级缓存的示例代码
MyBatis框架提供了二级缓存接口,我们只需要实现它再开启配置就可以使用了。
特别注意,我们要解决缓存穿透、缓存穿透和缓存雪崩的问题,同时也要保证缓存性能。
具体实现说明,直接看代码注释吧!
1、开启配置
SpringBoot配置
mybatis: configuration: cache-enabled:true
2、Redis配置以及服务接口
RedisConfig.java
packagecom.leven.mybatis.api.config; importcom.fasterxml.jackson.annotation.JsonAutoDetect; importcom.fasterxml.jackson.annotation.PropertyAccessor; importcom.fasterxml.jackson.databind.ObjectMapper; importorg.springframework.context.annotation.Bean; importorg.springframework.context.annotation.Configuration; importorg.springframework.data.redis.connection.RedisConnectionFactory; importorg.springframework.data.redis.core.RedisTemplate; importorg.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; importorg.springframework.data.redis.serializer.StringRedisSerializer; /** *Redis缓存配置 *@authorLeven *@date2019-09-07 */ @Configuration publicclassRedisConfig{ /** *配置自定义redisTemplate *@returnredisTemplate */ @Bean publicRedisTemplateredisTemplate(RedisConnectionFactoryredisConnectionFactory){ RedisTemplate template=newRedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值 Jackson2JsonRedisSerializer
RedisService.java
packagecom.leven.mybatis.core.service; importjava.util.*; importjava.util.concurrent.TimeUnit; /** *redis基础服务接口 *@authorLeven *@date2019-09-07 */ publicinterfaceRedisService{ //=============================common============================ /** *指定缓存失效时间 *@paramkey键 *@paramtime时间(秒) */ voidexpire(Stringkey,longtime); /** *指定缓存失效时间 *@paramkey键 *@paramexpireAt失效时间点 *@return处理结果 */ voidexpireAt(Stringkey,DateexpireAt); /** *根据key获取过期时间 *@paramkey键不能为null *@return时间(秒)返回0代表为永久有效 */ LonggetExpire(Stringkey); /** *判断key是否存在 *@paramkey键 *@returntrue存在false不存在 */ BooleanhasKey(Stringkey); /** *删除缓存 *@paramkey可以传一个值或多个 */ voiddelete(String...key); /** *删除缓存 *@paramkeys可以传一个值或多个 */ voiddelete(Collectionkeys); //============================String============================= /** *普通缓存获取 *@paramkey键 *@return值 */ Objectget(Stringkey); /** *普通缓存放入 *@paramkey键 *@paramvalue值 */ voidset(Stringkey,Objectvalue); /** *普通缓存放入并设置时间 *@paramkey键 *@paramvalue值 *@paramtime时间(秒)time要大于0如果time小于等于0将设置无限期 */ voidset(Stringkey,Objectvalue,longtime); /** *普通缓存放入并设置时间 *@paramkey键 *@paramvalue值 *@paramtime时间(秒)time要大于0如果time小于等于0将设置无限期 */ voidset(Stringkey,Objectvalue,longtime,TimeUnittimeUnit); /** *递增 *@paramkey键 *@paramvalue要增加几(大于0) *@return递增后结果 */ Longincr(Stringkey,longvalue); /** *递减 *@paramkey键 *@paramvalue要减少几(大于0) *@return递减后结果 */ Longdecr(Stringkey,longvalue); //================================Map================================= /** *HashGet *@paramkey键不能为null *@paramitem项不能为null *@return值 */ ObjecthashGet(Stringkey,Stringitem); /** *获取hashKey对应的所有键值 *@paramkey键 *@return对应的多个键值 */ Map
RedisServiceImpl.java
packagecom.leven.mybatis.core.service.impl; importcom.leven.commons.model.exception.SPIException; importcom.leven.mybatis.model.constant.Constant; importcom.leven.mybatis.core.service.RedisService; importcom.leven.mybatis.model.constant.ExceptionCode; importlombok.extern.slf4j.Slf4j; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.data.redis.core.RedisTemplate; importorg.springframework.stereotype.Service; importjava.util.*; importjava.util.concurrent.TimeUnit; /** *redis基础服务接口实现 *@authorLeven *@date2019-09-07 */ @Slf4j @Service publicclassRedisServiceImplimplementsRedisService{ /** * */ privatestaticfinalStringPREFIX=Constant.APPLICATION; @Autowired privateRedisTemplateredisTemplate; //=============================common============================ /** *指定缓存失效时间 *@paramkey键 *@paramtime时间(秒) */ @Override publicvoidexpire(Stringkey,longtime){ redisTemplate.expire(getKey(key),time,TimeUnit.SECONDS); } /** *指定缓存失效时间 *@paramkey键 *@paramexpireAt失效时间点 *@return处理结果 */ @Override publicvoidexpireAt(Stringkey,DateexpireAt){ redisTemplate.expireAt(getKey(key),expireAt); } /** *根据key获取过期时间 *@paramkey键不能为null *@return时间(秒)返回0代表为永久有效 */ @Override publicLonggetExpire(Stringkey){ returnredisTemplate.getExpire(getKey(key),TimeUnit.SECONDS); } /** *判断key是否存在 *@paramkey键 *@returntrue存在false不存在 */ @Override publicBooleanhasKey(Stringkey){ returnredisTemplate.hasKey(getKey(key)); } /** *删除缓存 *@paramkeys可以传一个值或多个 */ @Override publicvoiddelete(String...keys){ if(keys!=null&&keys.length>0){ if(keys.length==1){ redisTemplate.delete(getKey(keys[0])); }else{ List keyList=newArrayList<>(keys.length); for(Stringkey:keys){ keyList.add(getKey(key)); } redisTemplate.delete(keyList); } } } /** *删除缓存 *@paramkeys可以传一个值或多个 */ @Override publicvoiddelete(Collection keys){ if(keys!=null&&!keys.isEmpty()){ List keyList=newArrayList<>(keys.size()); for(Stringkey:keys){ keyList.add(getKey(key)); } redisTemplate.delete(keyList); } } //============================String============================= /** *普通缓存获取 *@paramkey键 *@return值 */ @Override publicObjectget(Stringkey){ returnkey==null?null:redisTemplate.opsForValue().get(getKey(key)); } /** *普通缓存放入 *@paramkey键 *@paramvalue值 */ @Override publicvoidset(Stringkey,Objectvalue){ redisTemplate.opsForValue().set(getKey(key),value); } /** *普通缓存放入并设置时间 *@paramkey键 *@paramvalue值 *@paramtime时间(秒)time要大于0如果time小于等于0将设置无限期 */ @Override publicvoidset(Stringkey,Objectvalue,longtime){ set(key,value,time,TimeUnit.SECONDS); } /** *普通缓存放入并设置时间 *@paramkey键 *@paramvalue值 *@paramtime时间time要大于0如果time小于等于0将设置无限期 *@paramtimeUnit时间单位 */ @Override publicvoidset(Stringkey,Objectvalue,longtime,TimeUnittimeUnit){ if(time>0){ redisTemplate.opsForValue().set(getKey(key),value,time,timeUnit); }else{ set(getKey(key),value); } } /** *递增 *@paramkey键 *@paramvalue要增加几(大于0) *@return递增后结果 */ @Override publicLongincr(Stringkey,longvalue){ if(value<1){ thrownewSPIException(ExceptionCode.RUNTIME_UNITE_EXP,"递增因子必须大于0"); } returnredisTemplate.opsForValue().increment(getKey(key),value); } /** *递减 *@paramkey键 *@paramvalue要减少几(大于0) *@return递减后结果 */ @Override publicLongdecr(Stringkey,longvalue){ if(value<1){ thrownewSPIException(ExceptionCode.RUNTIME_UNITE_EXP,"递减因子必须大于0"); } returnredisTemplate.opsForValue().decrement(getKey(key),value); } //================================Map================================= /** *HashGet *@paramkey键不能为null *@paramitem项不能为null *@return值 */ @Override publicObjecthashGet(Stringkey,Stringitem){ returnredisTemplate.opsForHash().get(getKey(key),item); } /** *获取hashKey对应的所有键值 *@paramkey键 *@return对应的多个键值 */ @Override publicMap hashEntries(Stringkey){ returnredisTemplate.opsForHash().entries(getKey(key)); } /** *HashSet *@paramkey键 *@parammap对应多个键值 */ @Override publicvoidhashSet(Stringkey,Map map){ redisTemplate.opsForHash().putAll(getKey(key),map); } /** *HashSet并设置时间 *@paramkey键 *@parammap对应多个键值 *@paramtime时间(秒) */ @Override publicvoidhashSet(Stringkey,Map map,longtime){ Stringk=getKey(key); redisTemplate.opsForHash().putAll(k,map); if(time>0){ expire(k,time); } } /** *向一张hash表中放入数据,如果不存在将创建 *@paramkey键 *@paramitem项 *@paramvalue值 */ @Override publicvoidhashSet(Stringkey,Stringitem,Objectvalue){ redisTemplate.opsForHash().putIfAbsent(getKey(key),item,value); } /** *向一张hash表中放入数据,如果不存在将创建 *@paramkey键 *@paramitem项 *@paramvalue值 *@paramtime时间(秒)注意:如果已存在的hash表有时间,这里将会替换原有的时间 */ @Override publicvoidhashSet(Stringkey,Stringitem,Objectvalue,longtime){ Stringk=getKey(key); redisTemplate.opsForHash().putIfAbsent(k,item,value); if(time>0){ expire(k,time); } } /** *删除hash表中的值 *@paramkey键不能为null *@paramitem项可以使多个不能为null */ @Override publicvoidhashDelete(Stringkey,Object...item){ redisTemplate.opsForHash().delete(getKey(key),item); } /** *删除hash表中的值 *@paramkey键不能为null *@paramitems项可以使多个不能为null */ @Override publicvoidhashDelete(Stringkey,Collectionitems){ redisTemplate.opsForHash().delete(getKey(key),items.toArray()); } /** *判断hash表中是否有该项的值 *@paramkey键不能为null *@paramitem项不能为null *@returntrue存在false不存在 */ @Override publicBooleanhashHasKey(Stringkey,Stringitem){ returnredisTemplate.opsForHash().hasKey(getKey(key),item); } /** *hash递增如果不存在,就会创建一个并把新增后的值返回 *@paramkey键 *@paramitem项 *@paramvalue要增加几(大于0) *@return递增后结果 */ @Override publicDoublehashIncr(Stringkey,Stringitem,doublevalue){ if(value<1){ thrownewSPIException(ExceptionCode.RUNTIME_UNITE_EXP,"递增因子必须大于0"); } returnredisTemplate.opsForHash().increment(getKey(key),item,value); } /** *hash递减 *@paramkey键 *@paramitem项 *@paramvalue要减少记(小于0) *@return递减后结果 */ @Override publicDoublehashDecr(Stringkey,Stringitem,doublevalue){ if(value<1){ thrownewSPIException(ExceptionCode.RUNTIME_UNITE_EXP,"递减因子必须大于0"); } returnredisTemplate.opsForHash().increment(getKey(key),item,-value); } //============================set============================= /** *根据key获取Set中的所有值 *@paramkey键 *@returnset集合 */ @Override publicSet setGet(Stringkey){ returnredisTemplate.opsForSet().members(getKey(key)); } /** *根据value从一个set中查询,是否存在 *@paramkey键 *@paramvalue值 *@returntrue存在false不存在 */ @Override publicBooleansetIsMember(Stringkey,Objectvalue){ returnredisTemplate.opsForSet().isMember(getKey(key),value); } /** *将数据放入set缓存 *@paramkey键 *@paramvalues值可以是多个 *@return成功个数 */ @Override publicLongsetAdd(Stringkey,Object...values){ returnredisTemplate.opsForSet().add(getKey(key),values); } /** *将数据放入set缓存 *@paramkey键 *@paramvalues值可以是多个 *@return成功个数 */ @Override publicLongsetAdd(Stringkey,Collectionvalues){ returnredisTemplate.opsForSet().add(getKey(key),values.toArray()); } /** *将set数据放入缓存 *@paramkey键 *@paramtime时间(秒) *@paramvalues值可以是多个 *@return成功个数 */ @Override publicLongsetAdd(Stringkey,longtime,Object...values){ Stringk=getKey(key); Longcount=redisTemplate.opsForSet().add(k,values); if(time>0){ expire(k,time); } returncount; } /** *获取set缓存的长度 *@paramkey键 *@returnset长度 */ @Override publicLongsetSize(Stringkey){ returnredisTemplate.opsForSet().size(getKey(key)); } /** *移除值为value的 *@paramkey键 *@paramvalues值可以是多个 *@return移除的个数 */ @Override publicLongsetRemove(Stringkey,Object...values){ returnredisTemplate.opsForSet().remove(getKey(key),values); } //===============================list================================= /** *获取list缓存的内容 *@paramkey键 *@paramstart开始 *@paramend结束0到-1代表所有值 *@return缓存列表 */ @Override publicList listRange(Stringkey,longstart,longend){ returnredisTemplate.opsForList().range(getKey(key),start,end); } /** *获取list缓存的长度 *@paramkey键 *@return长度 */ @Override publicLonglistSize(Stringkey){ returnredisTemplate.opsForList().size(getKey(key)); } /** *通过索引获取list中的值 *@paramkey键 *@paramindex索引index>=0时,0表头,1第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推 *@return值 */ @Override publicObjectlistIndex(Stringkey,longindex){ returnredisTemplate.opsForList().index(getKey(key),index); } /** *将list放入缓存 *@paramkey键 *@paramvalue值 */ @Override publicvoidlistRightPush(Stringkey,Objectvalue){ redisTemplate.opsForList().rightPush(getKey(key),value); } /** *将list放入缓存 *@paramkey键 *@paramvalue值 *@paramtime时间(秒) */ @Override publicvoidlistRightPush(Stringkey,Objectvalue,longtime){ Stringk=getKey(key); redisTemplate.opsForList().rightPush(k,value); if(time>0){ expire(k,time); } } /** *将list放入缓存 *@paramkey键 *@paramvalue值 */ @Override publicvoidlistRightPushAll(Stringkey,List value){ redisTemplate.opsForList().rightPushAll(getKey(key),value); } /** *将list放入缓存 * *@paramkey键 *@paramvalue值 *@paramtime时间(秒) */ @Override publicvoidlistRightPushAll(Stringkey,List value,longtime){ Stringk=getKey(key); redisTemplate.opsForList().rightPushAll(k,value); if(time>0){ expire(k,time); } } /** *根据索引修改list中的某条数据 *@paramkey键 *@paramindex索引 *@paramvalue值 */ @Override publicvoidlistSet(Stringkey,longindex,Objectvalue){ redisTemplate.opsForList().set(getKey(key),index,value); } /** *移除N个值为value *@paramkey键 *@paramcount移除多少个 *@paramvalue值 *@return移除的个数 */ @Override publicLonglistRemove(Stringkey,longcount,Objectvalue){ returnredisTemplate.opsForList().remove(getKey(key),count,value); } privateStringgetKey(Stringkey){ returnPREFIX+":"+key; } }
3、实现MyBatis的Cache接口
MybatisRedisCache.java
packagecom.leven.mybatis.core.cache; importcom.leven.commons.core.util.ApplicationContextUtils; importcom.leven.commons.model.exception.SPIException; importcom.leven.mybatis.core.service.RedisService; importcom.leven.mybatis.model.constant.ExceptionCode; importlombok.extern.slf4j.Slf4j; importorg.apache.commons.lang3.RandomUtils; importorg.apache.ibatis.cache.Cache; importjava.security.MessageDigest; importjava.util.HashSet; importjava.util.Map; importjava.util.Set; importjava.util.concurrent.TimeUnit; importjava.util.concurrent.locks.ReadWriteLock; importjava.util.concurrent.locks.ReentrantReadWriteLock; /** *MyBatis二级缓存Redis实现 *重点处理以下几个问题 *1、缓存穿透:存储空值解决,MyBatis框架实现 *2、缓存击穿:使用互斥锁,我们自己实现 *3、缓存雪崩:缓存有效期设置为一个随机范围,我们自己实现 *4、读写性能:rediskey不能过长,会影响性能,这里使用SHA-256计算摘要当成key *@authorLeven *@date2019-09-07 */ @Slf4j publicclassMybatisRedisCacheimplementsCache{ /** *统一字符集 */ privatestaticfinalStringCHARSET="utf-8"; /** *key摘要算法 */ privatestaticfinalStringALGORITHM="SHA-256"; /** *统一缓存头 */ privatestaticfinalStringCACHE_NAME="MyBatis:"; /** *读写锁:解决缓存击穿 */ privatefinalReadWriteLockreadWriteLock=newReentrantReadWriteLock(); /** *表空间ID:方便后面的缓存清理 */ privatefinalStringid; /** *redis服务接口:提供基本的读写和清理 */ privatestaticvolatileRedisServiceredisService; /** *信息摘要 */ privatevolatileMessageDigestmessageDigest; ///////////////////////解决缓存雪崩,具体范围根据业务需要设置合理值////////////////////////// /** *缓存最小有效期 */ privatestaticfinalintMIN_EXPIRE_MINUTES=60; /** *缓存最大有效期 */ privatestaticfinalintMAX_EXPIRE_MINUTES=120; /** *MyBatis给每个表空间初始化的时候要用到 *@paramid其实就是namespace的值 */ publicMybatisRedisCache(Stringid){ if(id==null){ thrownewIllegalArgumentException("CacheinstancesrequireanID"); } this.id=id; } /** *获取ID *@return真实值 */ @Override publicStringgetId(){ returnid; } /** *创建缓存 *@paramkey其实就是sql语句 *@paramvaluesql语句查询结果 */ @Override publicvoidputObject(Objectkey,Objectvalue){ try{ StringstrKey=getKey(key); //有效期为1~2小时之间随机,防止雪崩 intexpireMinutes=RandomUtils.nextInt(MIN_EXPIRE_MINUTES,MAX_EXPIRE_MINUTES); getRedisService().set(strKey,value,expireMinutes,TimeUnit.MINUTES); log.debug("Putcachetoredis,id={}",id); }catch(Exceptione){ log.error("Redisputfailed,id="+id,e); } } /** *读取缓存 *@paramkey其实就是sql语句 *@return缓存结果 */ @Override publicObjectgetObject(Objectkey){ try{ StringstrKey=getKey(key); log.debug("Getcachefromredis,id={}",id); returngetRedisService().get(strKey); }catch(Exceptione){ log.error("Redisgetfailed,failovertodb",e); returnnull; } } /** *删除缓存 *@paramkey其实就是sql语句 *@return结果 */ @Override publicObjectremoveObject(Objectkey){ try{ StringstrKey=getKey(key); getRedisService().delete(strKey); log.debug("Removecachefromredis,id={}",id); }catch(Exceptione){ log.error("Redisremovefailed",e); } returnnull; } /** *缓存清理 *网上好多博客这里用了flushDb甚至是flushAll,感觉好坑鸭! *应该是根据表空间进行清理 */ @Override publicvoidclear(){ try{ log.debug("clearcache,id={}",id); StringhsKey=CACHE_NAME+id; //获取CacheNamespace所有缓存key MapidMap=getRedisService().hashEntries(hsKey); if(!idMap.isEmpty()){ Set keySet=idMap.keySet(); Set keys=newHashSet<>(keySet.size()); keySet.forEach(item->keys.add(item.toString())); //清空CacheNamespace所有缓存 getRedisService().delete(keys); //清空CacheNamespace getRedisService().delete(hsKey); } }catch(Exceptione){ log.error("clearcachefailed",e); } } /** *获取缓存大小,暂时没用上 *@return长度 */ @Override publicintgetSize(){ return0; } /** *获取读写锁:为了解决缓存击穿 *@return锁 */ @Override publicReadWriteLockgetReadWriteLock(){ returnreadWriteLock; } /** *计算出key的摘要 *@paramcacheKeyCacheKey *@return字符串key */ privateStringgetKey(ObjectcacheKey){ StringcacheKeyStr=cacheKey.toString(); log.debug("counthashkey,cachekeyoriginstring:{}",cacheKeyStr); StringstrKey=byte2hex(getSHADigest(cacheKeyStr)); log.debug("hashkey:{}",strKey); Stringkey=CACHE_NAME+strKey; //在redis额外维护CacheNamespace创建的key,clear的时候只清理当前CacheNamespace的数据 getRedisService().hashSet(CACHE_NAME+id,key,"1"); returnkey; } /** *获取信息摘要 *@paramdata待计算字符串 *@return字节数组 */ privatebyte[]getSHADigest(Stringdata){ try{ if(messageDigest==null){ synchronized(MessageDigest.class){ if(messageDigest==null){ messageDigest=MessageDigest.getInstance(ALGORITHM); } } } returnmessageDigest.digest(data.getBytes(CHARSET)); }catch(Exceptione){ log.error("SHA-256digesterror:",e); thrownewSPIException(ExceptionCode.RUNTIME_UNITE_EXP,"SHA-256digesterror,id="+id+"."); } } /** *字节数组转16进制字符串 *@parambytes待转换数组 *@return16进制字符串 */ privateStringbyte2hex(byte[]bytes){ StringBuildersign=newStringBuilder(); for(byteaByte:bytes){ Stringhex=Integer.toHexString(aByte&0xFF); if(hex.length()==1){ sign.append("0"); } sign.append(hex.toUpperCase()); } returnsign.toString(); } /** *获取Redis服务接口 *使用双重检查保证线程安全 *@return服务实例 */ privateRedisServicegetRedisService(){ if(redisService==null){ synchronized(RedisService.class){ if(redisService==null){ redisService=ApplicationContextUtils.getBeanByClass(RedisService.class); } } } returnredisService; } }
到此这篇关于MyBatis整合Redis实现二级缓存的示例代码的文章就介绍到这了,更多相关MyBatis整合Redis二级缓存内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。