Springboot同时集成ehcache 和 redis等等做缓存,支持切换

实习 同时被 3 个专栏收录
20 篇文章 0 订阅
14 篇文章 0 订阅
3 篇文章 0 订阅


支持多级缓存进入,这里以ehcache和redis作为例子

1.导入相关依赖

  • 引入spring支持cache的依赖
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
  • 引入ehcache依赖
<dependency>
  <groupId>net.sf.ehcache</groupId>
  <artifactId>ehcache-core</artifactId>
  <version>x.x.x</version>
</dependency>
  • 引入redis依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

// jedis
<dependency>
   <groupId>redis.clients</groupId>
   <artifactId>jedis</artifactId>
   <version>x.x.x</version>
</dependency>

2.配置文件

  • 1.ehcache.xml文件,springboot会默认加载在resource文件夹下的ehcache.xml文件,如果在该路径下那么就不需要在application.properties文件中配置该文件路径,否则需要在配置文件中配置canche.config.path=
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" updateCheck="false">
    <!--cache属性:
        name:Cache的唯一标识
        maxElementsInMemory:内存中最大缓存对象数
        maxElementsOnDisk:磁盘中最大缓存对象数,若是0表示无穷大
        eternal:Element是否永久有效,一但设置了,timeout将不起作用
        overflowToDisk:配置此属性,当内存中Element数量达到maxElementsInMemory时,Ehcache将会Element写到磁盘中
        timeToIdleSeconds:设置Element在失效前的允许闲置时间。仅当element不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大
        timeToLiveSeconds:设置Element在失效前允许存活时间。最大时间介于创建时间和失效时间之间。仅当element不是永久有效时使用,默认是0.,也就是element存活时间无穷大
        diskPersistent:是否缓存虚拟机重启期数据
        diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒
        diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区
        memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)
    -->
    <defaultCache
            maxElementsInMemory="1000"
            maxElementsOnDisk="10000000"
            eternal="false"
            overflowToDisk="true"
            timeToIdleSeconds="600"
            timeToLiveSeconds="600"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            diskSpoolBufferSizeMB="30"
            memoryStoreEvictionPolicy="LRU"
            />

   
    <!--diskStore属性:
        user.home(用户的根目录)
        user.dir(用户当前的工作目录)
        java.io.tmpdir(默认的临时目录)
        ehcache.disk.store.dir(ehcache的配置目录)
    -->
    <diskStore path="user.dir"/>

</ehcache>
  • 2.redis相关配置
## 使用redis存储
spring.session.store-type=redis
#### Redis数据库索引(默认为0)
spring.redis.database=3
spring.redis.host=127.0.0.1
#### 密码默认为空
spring.redis.password=
spring.redis.port=6379
####连接超时时间 单位 ms(毫秒)
spring.redis.timeout=3000

# 缓存相关配置
# 这里如果启用redis作为缓存那么就写redis,如果用ehcache就写ehcache
spring.cache.type=redis
# 缓存唯一标识,对应之前ehcache的缓存名
spring.cache.cachenames=这里配置自己的缓存区域
# 设置过期时间在初始化时已配置
# 是否缓存null值,解决穿透问题
spring.cache.redis.cache-null-values=true

3.在初始化时指定cachemanager,根据配置文件中的redis/ehcache返回对应的cachemanager


/**
 * @author : wys
 * @date : 2021-04-07 10:33
 **/
@Configuration
@EnableCaching
public class CacheManagerCustomizerInitializer {

    @Bean
    public  CacheManagerCustomizer<EhCacheCacheManager> ehcacheManagerCustomizer() {
        return cacheManager -> {
            //自定义设置
            System.out.println("ehcache cacheManager customizer.");
        };
    }

    @Bean
    public CacheManagerCustomizer<RedisCacheManager> redisManagerCustomizer() {
        return cacheManager -> {
            // 设置默认的过期时间
            cacheManager.setDefaultExpiration(1800);
            System.out.println("redis cacheManager customizer.");
        };
    }

    // 自己定义一个 RedisTemplate,改变默认的jdk序列化方式,转为json,方便之后进行key的删除,并取消警告信息
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate template = new RedisTemplate();
        template.setConnectionFactory(factory);
        // Json序列化配置
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // String 的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }

}

4.如何使用

前面的配置基本已经完成,关于spring cache的基本用法网上很多。
在这里介绍一下如果删除对应redis的key,ehcache无影响,但是redis比较特殊,使用@CacheEvict可能对于redis无效,需要自己定义切面类来删除对应的cache相对来说比较方便

  • 先给出ehcache的删除方式,获取对应的cache,ehcache.getNativeCache().get(这里是你的key)

  • 利用注解@CacheEvict

  • 利用ehcache.evict()
    接下来再看看redis的方式

  • 获取需要利用到redisTemplate,redisTemplate.opsForValue().get(dcKey),看你是以什么方式存入进去

  • 删除:redisTemplate.delete(这里是你的key或者key的集合)
    这里给出模糊匹配key的批量删除方式,利用scan,关于keys的方法在数据量级的情况下也可以,比如自己个人学习时可以使用,但是一旦上了生产环境是严禁使用的,会造成系统堵塞,所以官方推荐使用scan的方式。
    具体可以看看这篇文章https://zhuanlan.zhihu.com/p/46353221

  • 1.方式一:

/**
     * 获取模糊匹配的集合
     * 这里要使用scan方法
     * 因为redis为单线程,使用keys会造成堵塞
     * @param matchKey
     * @return
     */
    public Set<String> scan(String matchKey) {
        Set<String> keys = (Set<String>) redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
            Set<String> keysTmp = new HashSet<>();
            Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder().match("*" + matchKey + "*").count(1000).build());
            while (cursor.hasNext()) {
                keysTmp.add(new String(cursor.next()));
            }
            return keysTmp;
        });

        return keys;
    }
  • 2.方式二::
public Set<Object> scan(String pattern) {
        redisTemplate.execute((RedisCallback<Set<Object>>) connection -> {
            JedisCommands commands = (JedisCommands) connection.getNativeConnection();
            MultiKeyCommands multiKeyCommands = (MultiKeyCommands) commands;
            int scanInvokeCount = 0;
            int totalCount = 0;
            WebLogUtils.debug("RedisHelper_clearScan_invoke_start scanInvokeCount:{}" + scanInvokeCount);
            ScanParams scanParams = new ScanParams();
            scanParams.match(pattern + "*");
            scanParams.count(500);
            ScanResult<String> scan = multiKeyCommands.scan("0", scanParams);
            scanInvokeCount++;
            while (null != scan.getStringCursor()) {
                List<String> keys = scan.getResult();
                if (!CollectionUtils.isEmpty(keys)){
                    int count = 0;
                    for (String key : keys) {
                        try {
                            connection.del(key.getBytes());
                            WebLogUtils.debug("RedisHelper_clearScan key:{}" + key);
                            count++;
                            totalCount++;
                        } catch (Exception e) {
                            WebLogUtils.debug("RedisHelper_clearScan_fail key:{}" + key);
                            e.printStackTrace();
                        }
                    }
                    WebLogUtils.debug("RedisHelper_clearScan_delete count:{}" + count);
                }
                if (!StringUtils.equals("0", scan.getStringCursor())) {
                    scan = multiKeyCommands.scan(scan.getStringCursor(), scanParams);
                    scanInvokeCount++;
                    WebLogUtils.debug("RedisHelper_clearScan_invoke scanInvokeCount:{}" + scanInvokeCount);
                    continue;
                } else {
                    break;
                }
            }
            WebLogUtils.debug("RedisHelper_clearScan_invoke_end totalCount:{}" + totalCount);
            WebLogUtils.debug("RedisHelper_clearScan_invoke_end scanInvokeCount:{}" + scanInvokeCount);
            return null;
        });
        return null;
    }

至此,你可以去项目中使用下看看效果,打断点,看第一次访问之后是否会继续进入方法,也可以通过打印日志的方式测试。使用redis可以直接查看有无对应key即可。

  • 1
    点赞
  • 0
    评论
  • 1
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

©️2022 CSDN 皮肤主题:护眼 设计师:闪电赇 返回首页

打赏作者

换名换一年

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值