SpringBoot整合Ehcache3的实现步骤

目录
  • 前言
  • 缓存配置
    • maven引用
    • 个性化配置
    • 代码注入配置
  • 缓存操作
    • 缓存预热
    • 更新操作
    • 查询操作
  • 缓存与数据库数据一致性

前言

公司部门老项目要迁移升级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: 1000
      offheap: 100
      disk: 500
      diskDir: 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 {
    @Autowired
    private EhcacheConfiguration ehcacheConfiguration;
    @Autowired
    private 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

@Async
    public 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标识,统一执行完方法体后执行缓存操作
将缓存操作以线程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");

    @Autowired
    private 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的实现步骤的文章就介绍到这了,更多相关SpringBoot整合Ehcache3内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • springboot ehcache 配置使用方法代码详解

    EhCache是一个比较成熟的Java缓存框架,最早从hibernate发展而来, 是进程中的缓存系统,它提供了用内存,磁盘文件存储,以及分布式存储方式等多种灵活的cache管理方案,快速简单. Springboot对ehcache的使用非常支持,所以在Springboot中只需做些配置就可使用,且使用方式也简易. 下面通过本文给大家介绍springboot ehcache 配置使用方法,具体内容如下所示: 1. pom 引入依赖 <!-- Ehcache --> <dependency

  • SpringBoot2整合Ehcache组件实现轻量级缓存管理

    目录 一.Ehcache缓存简介 Hibernate缓存 EhCache缓存特点 对比Redis缓存 二.集成SpringBoot框架 1.核心依赖 2.加载配置 3.配置详解 三.注解用法 四.源代码地址 一.Ehcache缓存简介 Hibernate缓存 Hibernate三级缓存机制简介: 一级缓存:基于Session级别分配一块缓存空间,缓存访问的对象信息.Session关闭后会自动清除缓存. 二级缓存:是SessionFactory对象缓存,可以被创建出的多个 Session 对象共享

  • SpringBoot中Shiro缓存使用Redis、Ehcache的方法

    SpringBoot 中配置redis作为session 缓存器. 让shiro引用 本文是建立在你是使用这shiro基础之上的补充内容 第一种:Redis缓存,将数据存储到redis 并且开启session存入redis中. 引入pom <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifac

  • springboot整合ehcache 实现支付超时限制的方法

    下面给大家介绍springboot整合ehcache 实现支付超时限制的方法,具体内容如下所示: <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache-core</artifactId> <version>2.6.11</version> </dependency> pom文件中引入ehcache依赖 在类路径下存放ehcache.

  • springboot整合EHCache的实践方案

    EhCache 是一个纯Java的进程内缓存框架,具有快速.精干等特点,是Hibernate中默认的CacheProvider. ehcache提供了多种缓存策略,主要分为内存和磁盘两级,所以无需担心容量问题. spring-boot是一个快速的集成框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置. 用户登录之后,几乎之后展示任何页面都需要显示一下用户信息.可以在用户登录成功之后将用户信息进行缓存,之后直

  • SpringBoot手动使用EhCache的方法示例

    SpringBoot在annotation的层面实现了数据缓存的功能,基于Spring的AOP技术.所有的缓存配置只是在annotation层面配置,像声明式事务一样. Spring定义了CacheManager和Cache接口统一不同的缓存技术.其中CacheManager是Spring提供的各种缓存技术的抽象接口.而Cache接口包含缓存的各种操作. CacheManger 针对不同的缓存技术,需要实现不同的cacheManager,Spring定义了如下的cacheManger实现. Ca

  • 使用ehcache三步搞定springboot缓存的方法示例

    本次内容主要介绍基于Ehcache 3.0来快速实现Spring Boot应用程序的数据缓存功能.在Spring Boot应用程序中,我们可以通过Spring Caching来快速搞定数据缓存.接下来我们将介绍如何在三步之内搞定Spring Boot缓存. 1. 创建一个Spring Boot工程并添加Maven依赖 你所创建的Spring Boot应用程序的maven依赖文件至少应该是下面的样子: <?xml version="1.0" encoding="UTF-8

  • 详解springboot整合ehcache实现缓存机制

    EhCache 是一个纯Java的进程内缓存框架,具有快速.精干等特点,是Hibernate中默认的CacheProvider. ehcache提供了多种缓存策略,主要分为内存和磁盘两级,所以无需担心容量问题. spring-boot是一个快速的集成框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置. 由于spring-boot无需任何样板化的配置文件,所以spring-boot集成一些其他框架时会有略微的

  • SpringBoot整合Ehcache3的实现步骤

    目录 前言 缓存配置 maven引用 个性化配置 代码注入配置 缓存操作 缓存预热 更新操作 查询操作 缓存与数据库数据一致性 前言 公司部门老项目要迁移升级java版本,需要进行缓存相关操作,原框架未支持这部分,经过调研java相关缓存方案大致分为ehcache和redis两种,redis的value最大值为500mb且超过1mb会对存取有性能影响,业务系统需要支持列表查询缓存就不可避免的涉及到大量的数据存取过滤,ehcache支持内存+磁盘缓存不用担心缓存容量问题,所以框架初步版本决定集成e

  • 使用sts工具、SpringBoot整合mybatis的详细步骤

    SpringBoot 集成 Mybatis 框架 一.1.SpringBoot 集成 Mybatis 的基本步骤 第一步:添加依赖: 第二步:配置数据源: 第三步:扫描接口包. 二.详细的集成步骤如下: 1.第一步:添加依赖: 添加依赖:除了常规依赖外,需要加入 Mybatis 代码如下(示例): <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XM

  • SpringBoot整合Drools的实现步骤

    Drools有什么用 从我个人所待过的公司,其中做智能酒店这个项目时就用到规则引擎Drools,将它用于处理优惠劵规则. SpringBoot整合Drools初步实战 1.导入Maven依赖 <properties> <drools.version>7.14.0.Final</drools.version> </properties> <!-- drools --> <dependency> <groupId>org.dr

  • SpringBoot整合MongoDB的实现步骤

    目录 一.技术介绍 1.MongoDB是什么? 二.使用步骤 1.引入maven库 2.具体使用示例 3.配置文件 4.单元测试 总结 一.技术介绍 1.MongoDB是什么? MongoDB(来自于英文单词"Humongous",中文含义为"庞大")是可以应用于各种规模的企业.各个行业以及各类应用程序的开源数据库.作为一个适用于敏捷开发的数据库,MongoDB的数据模式可以随着应用程序的发展而灵活地更新.与此同时,它也为开发人员 提供了传统数据库的功能:二级索引,

  • SpringBoot整合之SpringBoot整合MongoDB的详细步骤

    目录 一.创建项目,选择依赖 二.引入相关依赖(非必要) 三.如果是第一次使用MongoDB,首先先创建用户 四.定义核心配置文件 六.创建dao层,这里的dao层有两种写法 MongoDB 是一个基于分布式文件存储的数据库.由 C++ 语言编写.旨在为 WEB 应用提供可扩展的高性能数据存储解决方案. MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的.本文介绍SpringBoot整合之SpringBoot整合MongoDB的步骤. 一

  • SpringBoot整合Freemarker的基本步骤

    添加pom依赖 <!-- springboot整合freemarker --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency> 在application.yml中添加相关配置 # 配置freemarker spring:

  • SpringBoot整合Xxl-Job的完整步骤记录

    一.下载Xxl-Job源代码并导入本地并运行 Github地址: https://github.com/xuxueli/xxl-job 中文文档地址: https://www.xuxueli.com/xxl-job/ 1.使用Idea或Eclipse导入 2.执行sql脚本(红色标记处) 3.运行xxl-job-admin(xxl-job后台管理,主要方便管理各种各样的任务) 注意:在运行之前,需要把2的sql脚本执行完毕,并修改数据库连接池. 正常启动,访问地址为:http://localho

  • springboot整合flowable框架入门步骤

    最近工作中有用到工作流的开发,引入了flowable工作流框架,在此记录一下springboot整合flowable工作流框架的过程,以便后续再次使用到时可以做一些参考使用,如果项目中有涉及到流程审批的话,可以使用该框架帮我们实现流程图示化展示的功能,为了快速了解flowable工作流框架的一个使用过程,我们直接步入主题,springboot整合flowable工作流框架的步骤如下: 1.首先创建一个springboot工程,然后引入flowable pom依赖,代码如下: <dependenc

  • Springboot整合Urule的方法步骤

    摘要: Urule决策引擎可简化开发校验.决策类代码,底层由java语言实现,可基于SpringBoot快速配置,因为Urule工具目前为非常用工具,网上关于SpringBoot整合Urule资料匮乏,一直自己摸索,简单的环境搭建也费了些功夫,遇到些坑,作此记录 本次记录主要记录Urule-Serve端Urule-Client端分开部署的模式,这种使用场景也会更多:嵌入式成一个项目的配置和Urule-Server端一致. 一.Urule-Server端: 1.1. 基于maven的SpringB

  • SpringBoot 整合mapstruct的实现步骤

    目录 前言 mapstruct 使用来干什么的? mapstruct 相对于BeanUtils的优势在哪? 编码 引入依赖 创建 DTO.VO 运行测试用例 怎么解决mapstruct 失效呢? mapstruct常规操作 不同字段映射 LIST转换 总结 前言 在项目中,如果我们要遵循分层领域模型规约: 话,肯定避免不了在DTO.VO.BO.AO.VO.Query等实体的转换,我们通常有几种做法: 手动一个个字段的赋值通过反序列化的手段,必须先转成JSON字符串,再转回来使用Spring的Be

随机推荐