SpringBoot整合Ehcache3的实现步骤是什么
导读:本文共6416.5字符,通常情况下阅读需要21分钟。同时您也可以点击右侧朗读,来听本文内容。按键盘←(左) →(右) 方向键可以翻页。
摘要: 前言公司部门老项目要迁移升级java版本,需要进行缓存相关操作,原框架未支持这部分,经过调研java相关缓存方案大致分为ehcache和redis两种,redis的value最大值为500mb且超过1mb会对存取有性能影响,业务系统需要支持列表查询缓存就不可避免的涉及到大量的数据存取过滤,ehcache支持内存+磁盘缓存不用担心缓存容量问题,所以框架初步版本决定... ...
目录
(为您整理了一些要点),点击可以直达。前言
公司部门老项目要迁移升级java版本,需要进行缓存相关操作,原框架未支持这部分,经过调研java相关缓存方案大致分为ehcache和redis两种,redis的value最大值为500mb且超过1mb会对存取有性能影响,业务系统需要支持列表查询缓存就不可避免的涉及到大量的数据存取过滤,ehcache支持内存+磁盘缓存不用担心缓存容量问题,所以框架初步版本决定集成ehcache3,设计流程结构如下图所示
缓存配置
maven引用
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency><dependency><groupId>org.ehcache</groupId><artifactId>ehcache</artifactId></dependency>
个性化配置
#缓存配置cache:ehcache:heap:1000offheap:100disk:500diskDir:tempfiles/cache/
@Component@ConfigurationProperties("frmae.cache.ehcache")publicclassEhcacheConfiguration{/***ehcacheheap大小*jvm内存中缓存的key数量*/privateintheap;/***ehcacheoffheap大小*堆外内存大小,单位:MB*/privateintoffheap;/***磁盘持久化目录*/privateStringdiskDir;/***ehcachedisk*持久化到磁盘的大小,单位:MB*diskDir有效时才生效*/privateintdisk;publicEhcacheConfiguration(){heap=1000;offheap=100;disk=500;diskDir="tempfiles/cache/";}}
代码注入配置
因为springboot默认缓存优先注入redis配置,所以需要手动声明bean进行注入,同时ehcache的value值必须支持序列化接口,不能使用Object代替,这里声明一个缓存基类,所有缓存value对象必须继承该类
publicclassBaseSystemObjectimplementsSerializable{}
@Configuration@EnableCachingpublicclassEhcacheConfig{@AutowiredprivateEhcacheConfigurationehcacheConfiguration;@AutowiredprivateApplicationContextcontext;@Bean(name="ehCacheManager")publicCacheManagergetCacheManager(){//资源池生成器配置持久化ResourcePoolsBuilderresourcePoolsBuilder=ResourcePoolsBuilder.newResourcePoolsBuilder()//堆内缓存大小.heap(ehcacheConfiguration.getHeap(),EntryUnit.ENTRIES)//堆外缓存大小.offheap(ehcacheConfiguration.getOffheap(),MemoryUnit.MB)//文件缓存大小.disk(ehcacheConfiguration.getDisk(),MemoryUnit.MB);//生成配置ExpiryPolicyexpiryPolicy=ExpiryPolicyBuilder.noExpiration();CacheConfigurationconfig=CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class,BaseSystemObject.class,resourcePoolsBuilder)//设置永不过期.withExpiry(expiryPolicy).build();CacheManagerBuildercacheManagerBuilder=CacheManagerBuilder.newCacheManagerBuilder().with(CacheManagerBuilder.persistence(ehcacheConfiguration.getDiskDir()));returncacheManagerBuilder.build(true);}}
缓存操作
缓存预热
针对缓存框架选择的双写策略,即数据库和缓存同时写入,所以在系统启动时需要预先将数据库数据加载到缓存中
针对单表声明自定义注解,个性化缓存定义自定义接口
@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Inheritedpublic@interfaceHPCache{}
publicinterfaceIHPCacheInitService{StringgetCacheName();voidinitCache();}
系统初始化时同步进行缓存初始化,扫描注解实体类与接口实现Bean
@AsyncpublicvoidinitCache(ClassruntimeClass,List<String>extraPackageNameList){List<Class<?>>cacheEntityList=newArrayList<>();if(!runtimeClass.getPackage().getName().equals(Application.class.getPackage().getName())){cacheEntityList.addAll(ScanUtil.getAllClassByPackageName_Annotation(runtimeClass.getPackage(),HPCache.class));}for(StringpackageName:extraPackageNameList){cacheEntityList.addAll(ScanUtil.getAllClassByPackageName_Annotation(packageName,HPCache.class));}for(Classclazz:cacheEntityList){TableNametableName=(TableName)clazz.getAnnotation(TableName.class);List<LinkedHashMap<String,Object>>resultList=commonDTO.selectList(tableName.value(),"*","1=1","",newHashMap<>(),false);for(LinkedHashMap<String,Object>map:resultList){Cachecache=cacheManager.getCache(clazz.getName(),String.class,BaseSystemObject.class);Stringunitguid=ConvertOp.convert2String(map.get("UnitGuid"));try{Objectobj=clazz.newInstance();obj=ConvertOp.convertLinkHashMapToBean(map,obj);cache.put(unitguid,obj);}catch(Exceptione){e.printStackTrace();}}}//自定义缓存Map<String,IHPCacheInitService>res=context.getBeansOfType(IHPCacheInitService.class);for(Map.Entryen:res.entrySet()){IHPCacheInitServiceservice=(IHPCacheInitService)en.getValue();service.initCache();}System.out.println("缓存初始化完毕");}
需要注意,在EhcacheConfig配置类中需要进行缓存名称的提前注册,否则会导致操作缓存时空指针异常
Map<String,Object>annotatedBeans=context.getBeansWithAnnotation(SpringBootApplication.class);ClassruntimeClass=annotatedBeans.values().toArray()[0].getClass();//do,dao扫描List<String>extraPackageNameList=newArrayList<String>();extraPackageNameList.add(Application.class.getPackage().getName());List<Class<?>>cacheEntityList=newArrayList<>();if(!runtimeClass.getPackage().getName().equals(Application.class.getPackage().getName())){cacheEntityList.addAll(ScanUtil.getAllClassByPackageName_Annotation(runtimeClass.getPackage(),HPCache.class));}for(StringpackageName:extraPackageNameList){cacheEntityList.addAll(ScanUtil.getAllClassByPackageName_Annotation(packageName,HPCache.class));}for(Classclazz:cacheEntityList){cacheManagerBuilder=cacheManagerBuilder.withCache(clazz.getName(),config);}//自定义缓存Map<String,IHPCacheInitService>res=context.getBeansOfType(IHPCacheInitService.class);for(Map.Entryen:res.entrySet()){IHPCacheInitServiceservice=(IHPCacheInitService)en.getValue();cacheManagerBuilder=cacheManagerBuilder.withCache(service.getCacheName(),config);}
更新操作
手动获取ehcache的bean对象,调用put,repalce,delete方法进行操作
privateCacheManagercacheManager=(CacheManager)SpringBootBeanUtil.getBean("ehCacheManager");publicvoidexecuteUpdateOperation(StringcacheName,Stringkey,BaseSystemObjectvalue){Cachecache=cacheManager.getCache(cacheName,String.class,BaseSystemObject.class);if(cache.containsKey(key)){cache.replace(key,value);}else{cache.put(key,value);}}publicvoidexecuteDeleteOperation(StringcacheName,Stringkey){Cachecache=cacheManager.getCache(cacheName,String.class,BaseSystemObject.class);cache.remove(key);}
查询操作
缓存存储单表以主键—object形式存储,个性化缓存为key-object形式存储,单条记录可以通过getCache方法查询,列表查询需要取出整个缓存按条件进行过滤
publicObjectgetCache(StringcacheName,Stringkey){Cachecache=cacheManager.getCache(cacheName,String.class,BaseSystemObject.class);returncache.get(key);}publicList<Object>getAllCache(StringcacheName){Listresult=newArrayList<>();Cachecache=cacheManager.getCache(cacheName,String.class,BaseSystemObject.class);Iteratoriter=cache.iterator();while(iter.hasNext()){Cache.Entryentry=(Cache.Entry)iter.next();result.add(entry.getValue());}returnresult;}
缓存与数据库数据一致性
数据库数据操作与缓存操作顺序为先操作数据后操作缓存,在开启数据库事务的情况下针对单条数据单次操作是没有问题的,如果是组合操作一旦数据库操作发生异常回滚,缓存并没有回滚就会导致数据的不一致,比如执行顺序为dbop1=》cacheop1=》dbop2=》cacheop2,dbop2异常,cacheop1的操作已经更改了缓存
这里选择的方案是在数据库全部执行完毕后统一操作缓存,这个方案有一个缺点是如果缓存操作发生异常还是会出现上述问题,实际过程中缓存只是对内存的操作异常概率较小,对缓存操作持乐观状态,同时我们提供手动重置缓存的功能,算是一个折中方案,下面概述该方案的一个实现
声明自定义缓存事务注解
@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documented@Inheritedpublic@interfaceCacheTransactional{}
声明切面监听,在标记了CacheTransactional注解的方法执行前进行Redis标识,统一执行完方法体后执行缓存操作
将缓存操作以线程id区分放入待执行队列中序列化到redis,提供方法统一操作
publicclassCacheExecuteModelimplementsSerializable{privateStringobejctClazzName;privateStringcacheName;privateStringkey;privateBaseSystemObjectvalue;privateStringexecuteType;}privateCacheManagercacheManager=(CacheManager)SpringBootBeanUtil.getBean("ehCacheManager");@AutowiredprivateRedisUtilredisUtil;publicvoidputCacheIntoTransition(){StringthreadID=Thread.currentThread().getName();System.out.println("initthreadid:"+threadID);CacheExecuteModelcacheExecuteModel=newCacheExecuteModel();cacheExecuteModel.setExecuteType("option");redisUtil.redisTemplateSetForCollection(threadID,cacheExecuteModel,GlobalEnum.RedisDBNum.Cache.get_value());redisUtil.setExpire(threadID,5,TimeUnit.MINUTES,GlobalEnum.RedisDBNum.Cache.get_value());}publicvoidputCache(StringcacheName,Stringkey,BaseSystemObjectvalue){if(checkCacheOptinionInTransition()){StringthreadID=Thread.currentThread().getName();CacheExecuteModelcacheExecuteModel=newCacheExecuteModel("update",cacheName,key,value.getClass().getName(),value);redisUtil.redisTemplateSetForCollection(threadID,cacheExecuteModel,GlobalEnum.RedisDBNum.Cache.get_value());redisUtil.setExpire(threadID,5,TimeUnit.MINUTES,GlobalEnum.RedisDBNum.Cache.get_value());}else{executeUpdateOperation(cacheName,key,value);}}publicvoiddeleteCache(StringcacheName,Stringkey){if(checkCacheOptinionInTransition()){StringthreadID=Thread.currentThread().getName();CacheExecuteModelcacheExecuteModel=newCacheExecuteModel("delete",cacheName,key);redisUtil.redisTemplateSetForCollection(threadID,cacheExecuteModel,GlobalEnum.RedisDBNum.Cache.get_value());redisUtil.setExpire(threadID,5,TimeUnit.MINUTES,GlobalEnum.RedisDBNum.Cache.get_value());}else{executeDeleteOperation(cacheName,key);}}publicvoidexecuteOperation(){StringthreadID=Thread.currentThread().getName();if(checkCacheOptinionInTransition()){List<LinkedHashMap>executeList=redisUtil.redisTemplateGetForCollectionAll(threadID,GlobalEnum.RedisDBNum.Cache.get_value());for(LinkedHashMapobj:executeList){StringexecuteType=ConvertOp.convert2String(obj.get("executeType"));if(executeType.contains("option")){continue;}StringobejctClazzName=ConvertOp.convert2String(obj.get("obejctClazzName"));StringcacheName=ConvertOp.convert2String(obj.get("cacheName"));Stringkey=ConvertOp.convert2String(obj.get("key"));LinkedHashMapvalueMap=(LinkedHashMap)obj.get("value");StringvalueMapJson=JSON.toJSONString(valueMap);try{ObjectvalueInstance=JSON.parseObject(valueMapJson,Class.forName(obejctClazzName));if(executeType.equals("update")){executeUpdateOperation(cacheName,key,(BaseSystemObject)valueInstance);}elseif(executeType.equals("delete")){executeDeleteOperation(cacheName,key);}}catch(Exceptione){e.printStackTrace();}}redisUtil.redisTemplateRemove(threadID,GlobalEnum.RedisDBNum.Cache.get_value());}}publicbooleancheckCacheOptinionInTransition(){StringthreadID=Thread.currentThread().getName();System.out.println("checkthreadid:"+threadID);returnredisUtil.isValid(threadID,GlobalEnum.RedisDBNum.Cache.get_value());}
</div> <div class="zixun-tj-product adv-bottom"></div> </div> </div> <div class="prve-next-news">
SpringBoot整合Ehcache3的实现步骤是什么的详细内容,希望对您有所帮助,信息来源于网络。