SpringBoot整合Ehcache3

2024-09-06 13:18

本文主要是介绍SpringBoot整合Ehcache3,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

公司部门老项目要迁移升级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")
public class EhcacheConfiguration {/*** ehcache heap大小* jvm内存中缓存的key数量*/private int heap;/*** ehcache offheap大小* 堆外内存大小, 单位: MB*/private int offheap;/*** 磁盘持久化目录*/private String diskDir;/*** ehcache disk* 持久化到磁盘的大小, 单位: MB* diskDir有效时才生效*/private int disk;public EhcacheConfiguration(){heap = 1000;offheap = 100;disk = 500;diskDir = "tempfiles/cache/";}
}

代码注入配置

因为springboot默认缓存优先注入redis配置,所以需要手动声明bean进行注入,同时ehcache的value值必须支持序列化接口,不能使用Object代替,这里声明一个缓存基类,所有缓存value对象必须继承该类

public class BaseSystemObject implements Serializable {}
@Configuration
@EnableCaching
public class EhcacheConfig {@Autowiredprivate EhcacheConfiguration ehcacheConfiguration;@Autowiredprivate ApplicationContext context;@Bean(name = "ehCacheManager")public CacheManager getCacheManager() {//资源池生成器配置持久化ResourcePoolsBuilder resourcePoolsBuilder = 				  ResourcePoolsBuilder.newResourcePoolsBuilder()// 堆内缓存大小.heap(ehcacheConfiguration.getHeap(), EntryUnit.ENTRIES)// 堆外缓存大小.offheap(ehcacheConfiguration.getOffheap(), MemoryUnit.MB)// 文件缓存大小.disk(ehcacheConfiguration.getDisk(), MemoryUnit.MB);//生成配置ExpiryPolicy expiryPolicy = ExpiryPolicyBuilder.noExpiration();CacheConfiguration config = CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, BaseSystemObject.class, resourcePoolsBuilder)//设置永不过期.withExpiry(expiryPolicy).build();CacheManagerBuilder cacheManagerBuilder = CacheManagerBuilder.newCacheManagerBuilder().with(CacheManagerBuilder.persistence(ehcacheConfiguration.getDiskDir()));return cacheManagerBuilder.build(true);}
}

缓存操作

缓存预热

针对缓存框架选择的双写策略,即数据库和缓存同时写入,所以在系统启动时需要预先将数据库数据加载到缓存中

针对单表声明自定义注解,个性化缓存定义自定义接口

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface HPCache {}
public interface IHPCacheInitService {String getCacheName();void initCache();
}

系统初始化时同步进行缓存初始化,扫描注解实体类与接口实现Bean

@Asyncpublic void initCache(Class runtimeClass, List<String> extraPackageNameList) {List<Class<?>> cacheEntityList = new ArrayList<>();if (!runtimeClass.getPackage().getName().equals(Application.class.getPackage().getName())) {cacheEntityList.addAll(ScanUtil.getAllClassByPackageName_Annotation(runtimeClass.getPackage(), HPCache.class));}for (String packageName : extraPackageNameList) {cacheEntityList.addAll(ScanUtil.getAllClassByPackageName_Annotation(packageName, HPCache.class));}for (Class clazz : cacheEntityList) {TableName tableName = (TableName) clazz.getAnnotation(TableName.class);List<LinkedHashMap<String, Object>> resultList = commonDTO.selectList(tableName.value(), "*", "1=1", "", new HashMap<>(), false);for (LinkedHashMap<String, Object> map : resultList) {Cache cache = cacheManager.getCache(clazz.getName(), String.class, BaseSystemObject.class);String unitguid = ConvertOp.convert2String(map.get("UnitGuid"));try {Object obj = clazz.newInstance();obj = ConvertOp.convertLinkHashMapToBean(map, obj);cache.put(unitguid, obj);} catch (Exception e) {e.printStackTrace();}}}//自定义缓存Map<String, IHPCacheInitService> res = context.getBeansOfType(IHPCacheInitService.class);for (Map.Entry en : res.entrySet()) {IHPCacheInitService service = (IHPCacheInitService) en.getValue();service.initCache();}System.out.println("缓存初始化完毕");}

需要注意,在EhcacheConfig配置类中需要进行缓存名称的提前注册,否则会导致操作缓存时空指针异常

    Map<String, Object> annotatedBeans = context.getBeansWithAnnotation(SpringBootApplication.class);Class runtimeClass = annotatedBeans.values().toArray()[0].getClass();//do,dao扫描List<String> extraPackageNameList = new ArrayList<String>();extraPackageNameList.add(Application.class.getPackage().getName());List<Class<?>> cacheEntityList = new ArrayList<>();if (!runtimeClass.getPackage().getName().equals(Application.class.getPackage().getName())) {cacheEntityList.addAll(ScanUtil.getAllClassByPackageName_Annotation(runtimeClass.getPackage(), HPCache.class));}for (String packageName : extraPackageNameList) {cacheEntityList.addAll(ScanUtil.getAllClassByPackageName_Annotation(packageName, HPCache.class));}for (Class clazz : cacheEntityList) {cacheManagerBuilder = cacheManagerBuilder.withCache(clazz.getName(), config);}//自定义缓存Map<String, IHPCacheInitService> res = context.getBeansOfType(IHPCacheInitService.class);for (Map.Entry en :res.entrySet()) {IHPCacheInitService service = (IHPCacheInitService)en.getValue();cacheManagerBuilder = cacheManagerBuilder.withCache(service.getCacheName(), config);}

更新操作

手动获取ehcache的bean对象,调用put,repalce,delete方法进行操作

   	private  CacheManager cacheManager = (CacheManager) SpringBootBeanUtil.getBean("ehCacheManager");public void executeUpdateOperation(String cacheName, String key, BaseSystemObject value) {Cache cache = cacheManager.getCache(cacheName, String.class, BaseSystemObject.class);if (cache.containsKey(key)) {cache.replace(key, value);} else {cache.put(key, value);}}public void executeDeleteOperation(String cacheName, String key) {Cache cache = cacheManager.getCache(cacheName, String.class, BaseSystemObject.class);cache.remove(key);}

查询操作

缓存存储单表以主键—object形式存储,个性化缓存为key-object形式存储,单条记录可以通过getCache方法查询,列表查询需要取出整个缓存按条件进行过滤

 public Object getCache(String cacheName, String key){Cache cache = cacheManager.getCache(cacheName, String.class, BaseSystemObject.class);return cache.get(key);}public List<Object> getAllCache(String cacheName){List result = new ArrayList<>();Cache cache = cacheManager.getCache(cacheName, String.class, BaseSystemObject.class);Iterator iter = cache.iterator();while (iter.hasNext()) {Cache.Entry entry = (Cache.Entry) iter.next();result.add(entry.getValue());}return result;}

缓存与数据库数据一致性

数据库数据操作与缓存操作顺序为先操作数据后操作缓存,在开启数据库事务的情况下针对单条数据单次操作是没有问题的,如果是组合操作一旦数据库操作发生异常回滚,缓存并没有回滚就会导致数据的不一致,比如执行顺序为dbop1=》cacheop1=》dbop2=》cacheop2,dbop2异常,cacheop1的操作已经更改了缓存

这里选择的方案是在数据库全部执行完毕后统一操作缓存,这个方案有一个***缺点是如果缓存操作发生异常还是会出现上述问题***,实际过程中缓存只是对内存的操作异常概率较小,对缓存操作持乐观状态,同时我们提供手动重置缓存的功能,算是一个折中方案,下面概述该方案的一个实现

声明自定义缓存事务注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface CacheTransactional {}

声明切面监听,在标记了CacheTransactional注解的方法执行前进行Redis标识,统一执行完方法体后执行缓存操作

@Aspect
@Component
@Order(value = 101)
public class CacheExecuteAspect {@Autowiredprivate CacheExecuteUtil cacheExecuteUtil;/*** 切面点 指定注解*/@Pointcut("@annotation(com.haopan.frame.common.annotation.CacheTransactional) " +"|| @within(com.haopan.frame.common.annotation.CacheTransactional)")public void cacheExecuteAspect() {}/*** 拦截方法指定为 repeatSubmitAspect*/@Around("cacheExecuteAspect()")public Object around(ProceedingJoinPoint point) throws Throwable {MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();CacheTransactional cacheTransactional = method.getAnnotation(CacheTransactional.class);if (cacheTransactional != null) {cacheExecuteUtil.putCacheIntoTransition();try{Object obj = point.proceed();cacheExecuteUtil.executeOperation();return obj;}catch (Exception e){e.printStackTrace();throw  e;}} else {return point.proceed();}}
}

将缓存操作以线程id区分放入待执行队列中序列化到redis,提供方法统一操作

public class CacheExecuteModel implements Serializable {private String obejctClazzName;private String cacheName;private String key;private BaseSystemObject value;private String executeType;
}
private  CacheManager cacheManager = (CacheManager) SpringBootBeanUtil.getBean("ehCacheManager");@Autowiredprivate RedisUtil redisUtil;public void putCacheIntoTransition(){String threadID = Thread.currentThread().getName();System.out.println("init threadid:"+threadID);CacheExecuteModel cacheExecuteModel = new CacheExecuteModel();cacheExecuteModel.setExecuteType("option");redisUtil.redisTemplateSetForCollection(threadID,cacheExecuteModel, GlobalEnum.RedisDBNum.Cache.get_value());redisUtil.setExpire(threadID,5, TimeUnit.MINUTES, GlobalEnum.RedisDBNum.Cache.get_value());}public void putCache(String cacheName, String key, BaseSystemObject value) {if(checkCacheOptinionInTransition()){String threadID = Thread.currentThread().getName();CacheExecuteModel cacheExecuteModel = new CacheExecuteModel("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);}}public void deleteCache(String cacheName, String key) {if(checkCacheOptinionInTransition()){String threadID = Thread.currentThread().getName();CacheExecuteModel cacheExecuteModel = new CacheExecuteModel("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);}}public void executeOperation(){String threadID = Thread.currentThread().getName();if(checkCacheOptinionInTransition()){List<LinkedHashMap> executeList =  redisUtil.redisTemplateGetForCollectionAll(threadID, GlobalEnum.RedisDBNum.Cache.get_value());for (LinkedHashMap obj:executeList) {String executeType = ConvertOp.convert2String(obj.get("executeType"));if(executeType.contains("option")){continue;}String obejctClazzName = ConvertOp.convert2String(obj.get("obejctClazzName"));String cacheName = ConvertOp.convert2String(obj.get("cacheName"));String key = ConvertOp.convert2String(obj.get("key"));LinkedHashMap valueMap = (LinkedHashMap)obj.get("value");String valueMapJson =  JSON.toJSONString(valueMap);try{Object valueInstance = JSON.parseObject(valueMapJson,Class.forName(obejctClazzName));if(executeType.equals("update")){executeUpdateOperation(cacheName,key,(BaseSystemObject)valueInstance);}else if(executeType.equals("delete")){executeDeleteOperation(cacheName,key);}}catch (Exception e){e.printStackTrace();}}redisUtil.redisTemplateRemove(threadID,GlobalEnum.RedisDBNum.Cache.get_value());}}public boolean checkCacheOptinionInTransition(){String threadID = Thread.currentThread().getName();System.out.println("check threadid:"+threadID);return redisUtil.isValid(threadID, GlobalEnum.RedisDBNum.Cache.get_value());}

这篇关于SpringBoot整合Ehcache3的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1142107

相关文章

Spring事务传播机制最佳实践

《Spring事务传播机制最佳实践》Spring的事务传播机制为我们提供了优雅的解决方案,本文将带您深入理解这一机制,掌握不同场景下的最佳实践,感兴趣的朋友一起看看吧... 目录1. 什么是事务传播行为2. Spring支持的七种事务传播行为2.1 REQUIRED(默认)2.2 SUPPORTS2

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Java进程异常故障定位及排查过程

《Java进程异常故障定位及排查过程》:本文主要介绍Java进程异常故障定位及排查过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、故障发现与初步判断1. 监控系统告警2. 日志初步分析二、核心排查工具与步骤1. 进程状态检查2. CPU 飙升问题3. 内存

java中新生代和老生代的关系说明

《java中新生代和老生代的关系说明》:本文主要介绍java中新生代和老生代的关系说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、内存区域划分新生代老年代二、对象生命周期与晋升流程三、新生代与老年代的协作机制1. 跨代引用处理2. 动态年龄判定3. 空间分

Java设计模式---迭代器模式(Iterator)解读

《Java设计模式---迭代器模式(Iterator)解读》:本文主要介绍Java设计模式---迭代器模式(Iterator),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录1、迭代器(Iterator)1.1、结构1.2、常用方法1.3、本质1、解耦集合与遍历逻辑2、统一

Java内存分配与JVM参数详解(推荐)

《Java内存分配与JVM参数详解(推荐)》本文详解JVM内存结构与参数调整,涵盖堆分代、元空间、GC选择及优化策略,帮助开发者提升性能、避免内存泄漏,本文给大家介绍Java内存分配与JVM参数详解,... 目录引言JVM内存结构JVM参数概述堆内存分配年轻代与老年代调整堆内存大小调整年轻代与老年代比例元空

深度解析Java DTO(最新推荐)

《深度解析JavaDTO(最新推荐)》DTO(DataTransferObject)是一种用于在不同层(如Controller层、Service层)之间传输数据的对象设计模式,其核心目的是封装数据,... 目录一、什么是DTO?DTO的核心特点:二、为什么需要DTO?(对比Entity)三、实际应用场景解析

Java 线程安全与 volatile与单例模式问题及解决方案

《Java线程安全与volatile与单例模式问题及解决方案》文章主要讲解线程安全问题的五个成因(调度随机、变量修改、非原子操作、内存可见性、指令重排序)及解决方案,强调使用volatile关键字... 目录什么是线程安全线程安全问题的产生与解决方案线程的调度是随机的多个线程对同一个变量进行修改线程的修改操

从原理到实战深入理解Java 断言assert

《从原理到实战深入理解Java断言assert》本文深入解析Java断言机制,涵盖语法、工作原理、启用方式及与异常的区别,推荐用于开发阶段的条件检查与状态验证,并强调生产环境应使用参数验证工具类替代... 目录深入理解 Java 断言(assert):从原理到实战引言:为什么需要断言?一、断言基础1.1 语

深度解析Java项目中包和包之间的联系

《深度解析Java项目中包和包之间的联系》文章浏览阅读850次,点赞13次,收藏8次。本文详细介绍了Java分层架构中的几个关键包:DTO、Controller、Service和Mapper。_jav... 目录前言一、各大包1.DTO1.1、DTO的核心用途1.2. DTO与实体类(Entity)的区别1