SpringBoot2.x 缓存---注解方式、整合RedisCluster

java 同时被 2 个专栏收录
21 篇文章 0 订阅
31 篇文章 0 订阅

SpringBoot2.x 缓存—注解方式、整合RedisCluster

やめてよ、やめてよ 、優しくしないでよ、どうしても僕には、理解ができないよ

在这里插入图片描述

《心做し》真是太好听啦 ❤️❤️❤️


前言:

最近重构项目,再次使用了一下spring缓存,在这里记录一下。在spring 3.1的时候,引入了对Cache的支持,和事务一样,其实就是做了一个方法aop。spring cache默认将传入参数作为key,将返回值作为value进行保存,如果调用方法有相应的key则不执行方法之间使用aop中的逻辑,即返回缓存结果。

spring cache支持xml和注解的方式,这里我们只谈注解方式


注解说明:

注解说明
@EnableCaching开启缓存功能,放在相应配置类或启动类上
@CacheConfig缓存配置,设置缓存名称
@Cacheable执行方法前先查询缓存,有则直接返回缓存数据,否则查询数据再将数据放入缓存;也可以放类上,表示该类所有方法都支持缓存
@CachePut执行新增或更新方法后,将数据放入缓存中(每次都会执行,保持缓存和数据始终一致)
@CacheEvict清除缓存
@Caching组合多个缓存操作

注解参数:

  • @EnableCaching
参数类型说明
proxyTargetClassboolean是否要基于cglib生成代理去实现缓存
modeAdviceMode选择缓存模式、默认是AdviceMode.PROXY 可以切换为 AdviceMode#ASPECTJ
orderint设置缓存管理器执行的顺序
  • @CacheConfig
参数类型说明
cacheNamesString[]缓存的名称,别名value
keyGeneratorString缓存key的生成器
cacheManagerString配置使用那个缓存管理器、和cacheResolver排斥
cacheResolverString定义使用那个拦截器、和cacheManager互斥
  • @Cacheable
参数类型说明
valueString[]缓存的名称,别名cacheNames
cacheNamesString[]缓存的名称,别名value
keyString缓存key的值、默认是以所有的参数作为key、也可以直接配置keyGenerator或者spel表达式
keyGeneratorString缓存key的生成器
cacheManagerString配置使用那个缓存管理器、和cacheResolver排斥
cacheResolverString定义使用那个拦截器、和cacheManager互斥
conditionString根据spel表达式来可以配置什么条件下进行缓存 默认全部缓存
unlessString和condition相反
syncboolean是否开启同步功能、默认不开启
  • @CachePut
参数类型说明
valueString[]缓存的名称,别名cacheNames
cacheNamesString[]缓存的名称,别名value
keyString缓存key的值、默认是以所有的参数作为key、也可以直接配置keyGenerator或者spel表达式
keyGeneratorString缓存key的生成器
cacheManagerString配置使用那个缓存管理器、和cacheResolver排斥
cacheResolverString定义使用那个拦截器、和cacheManager互斥
conditionString根据spel表达式来可以配置什么条件下进行缓存 默认全部缓存
unlessString和condition相反
  • @CacheEvict
参数类型说明
valueString[]缓存的名称,别名cacheNames
cacheNamesString[]缓存的名称,别名value
keyString缓存key的值、默认是以所有的参数作为key、也可以直接配置keyGenerator或者spel表达式
keyGeneratorString缓存key的生成器
cacheManagerString配置使用那个缓存管理器、和cacheResolver排斥
cacheResolverString定义使用那个拦截器、和cacheManager互斥
conditionString根据spel表达式来可以配置什么条件下进行缓存 默认全部缓存
allEntriesboolean是否删除所有键的缓存 默认不删除
beforeInvocationboolean是否在调用此方法前 删除缓存(不管方法执行成功与否都删除)
  • @Caching
参数类型说明
cacheableCacheable[]多个@Cacheable
putCachePut[]多个@CachePut
evictCacheEvict[]多个@CacheEvict

然后,我们这里介绍一下CacheManger,针对不同的缓存技术,需要实现不同的cacheManager,Spring定义了如下的cacheManger实现。

CacheManger介绍:

CacheManger描述
SimpleCacheManager使用简单的Collection来存储缓存,主要用于测试
ConcurrentMapCacheManager使用ConcurrentMap作为缓存技术(默认)
NoOpCacheManager测试用
EhCacheCacheManager使用EhCache作为缓存技术,以前在hibernate的时候经常用
GuavaCacheManager使用google guava的GuavaCache作为缓存技术
HazelcastCacheManager使用Hazelcast作为缓存技术
JCacheCacheManager使用JCache标准的实现作为缓存技术,如Apache Commons JCS
RedisCacheManager使用Redis作为缓存技术

你可以创建不同的CacheManger来配置多个缓存,然后通过CacheManger的bean名称来指定使用哪个CacheManger,类似:

    @Bean("redisCacheManager")
    @Primary
    public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
    ...
    }
    
    @Bean("guavaCacheManager") 
    public GuavaCacheManager guavaCacheManager(RedisTemplate redisTemplate) {
        ...
    } 

调用:

   @Cacheable(value = "redisCache",key="#key",cacheManager="redisCacheManager")
    public String cacheRedisTest(String key) {
        ...
        return key;
    }
    
    @Cacheable(value = "guavaCache",key="#key",cacheManager="guavaCacheManager")
    public String cacheGuavaTest(String key) {
        ...
        return key;
    }

使用本地缓存:

pom:

加上

<!--spring cache-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

@EnableCaching

加在启动类或者配置类上都可以

@SpringBootApplication
@EnableCaching
public class CacheDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(CacheDemoApplication.class, args);
    }

}

因为懒,我这里就不配置真实数据库了,用日志来判断方法是否执行?,正常使用再看数据库数据即可

@Repository
@CacheConfig(cacheNames = "testCache1,testCache2")
public class CacheDao {

    @Cacheable(value = "testCache1", key = "#key")
    public String cacheableTest(int key) {
        System.out.println("执行了cacheableTest方法");
        return "返回结果" + key + "啦";
    }

    @CachePut(value = "testCache2", key = "#key")
    public String cachePutTest(int key) {
        System.out.println("执行了cachePutTest方法");
        return "返回结果" + key + "啦";
    }

    @CacheEvict(value = "testCache1", allEntries = true, beforeInvocation = true)
    public String cacheEvictTest(int key) {
        System.out.println("执行了cacheEvictTest方法");
        return "返回结果" + key + "啦";
    }

    /**
     * 先清除,再加
     *
     * @param key
     * @return
     */
    @Caching(cacheable = @Cacheable(value = "testCache2", key = "#key"),
            evict = @CacheEvict(value = "testCache1", key = "#key"))
    public String cachingTest(int key) {
        System.out.println("执行了cachingTest方法");
        return "返回结果" + key + "啦";
    }
}

结果:

  1. 多次执行cacheableTest,可以看到只访问了一次方法
    在这里插入图片描述
  2. 多次执行cachePutTest,然后执行cacheableTest,可以看到cachePut每次都访问了方法
    在这里插入图片描述
  3. 执行cacheEvictTest,然后执行cacheableTest,可以看到cacheableTest再次调用了方法
    在这里插入图片描述
  4. caching可以进行多个缓存的操作

问题:

本地缓存的问题当然就是不支持分布式调用啦,下面我们看整合redis来进行分布式缓存。


整合RedisCluster配置分布式缓存:

pom:

将本地缓存配置换成相应的redis整合配置,并引入jedis

        <!--spring redis cache-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!--redis-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.10.2</version>
        </dependency>

@EnableCaching

可以将@EnableCaching移到配置类上,*处填上相应的RedisCluster的host和password

@Configuration
@EnableCaching
public class RedisCacheConfig {
    @Bean
    public RedisClusterConfiguration getRedisCluster() {
        RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration();
        Set<RedisNode> jedisClusterNodes = new HashSet<>();
        String[] add = "***************".split(",");
        for (String temp : add) {
            String[] hostAndPort = StringUtils.split(temp, ":");
            jedisClusterNodes.add(new RedisNode(hostAndPort[0], Integer.parseInt(hostAndPort[1])));
        }
        redisClusterConfiguration.setClusterNodes(jedisClusterNodes);
        redisClusterConfiguration.setPassword("*******");
        return redisClusterConfiguration;
    }

    @Bean
    public JedisPoolConfig getJedisPoolConfig() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(8);
        jedisPoolConfig.setMaxIdle(8);
        jedisPoolConfig.setMaxWaitMillis(3000);
        return jedisPoolConfig;
    }

    @Bean
    public JedisConnectionFactory redisConnectionFactory(RedisClusterConfiguration redisClusterConfiguration, JedisPoolConfig jedisPoolConfig) {
        return new JedisConnectionFactory(redisClusterConfiguration, jedisPoolConfig);
    }

    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory cf) {
        RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>();
        redisTemplate.setConnectionFactory(cf);

        //序列化配置
//        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);
//        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
//        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }

    //定制缓存管理器的属性,自行做一些扩展和定制
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        //设置缓存过期时间1天
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofDays(1));
        return RedisCacheManager
                .builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
                .cacheDefaults(redisCacheConfiguration).build();
    }
}

结果:

好了,大功告成,是不是非常简单,我们看看效果:

  1. 多次执行cacheableTest,可以看到只访问了一次方法,并且redis当中存了此信息
    在这里插入图片描述
    在这里插入图片描述
  2. 执行cacheEvictTest,看到redis删除了此信息
    在这里插入图片描述

问题:

  • 因为本质是aop配置,所以调用相同类的方法会失效,一般看到两种方式:

1、缓存配置在dao层(我一般这样)
2、将自身注入


  • 这里特别提一下Springboot 2.x版本的RedisCacheManager 类的配置,和1.x不太一样
  1. 1.x 配置方式
@Bean
public CacheManager cacheManager(RedisTemplate redisTemplate) {
    RedisCacheManager cacheManager= new RedisCacheManager(redisTemplate);
    Map<String,Long> expiresMap=new HashMap<>();
    expiresMap.put("Product",5L);
    cacheManager.setExpires(expiresMap);
    return cacheManager;
}
  1. 2.x 配置方式如之前代码
@Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        //设置缓存过期时间1天
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofDays(1));
        return RedisCacheManager
                .builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
                .cacheDefaults(redisCacheConfiguration).build();
    }

或者不用builder模式,用构造函数也行:


    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        //设置缓存过期时间
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(30));
        RedisCacheManager cacheManager = new RedisCacheManager(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory), redisCacheConfiguration);
        return cacheManager;
    }

总结:

总的来说,spring给我提供了这个机制还是很方便的,其实我们还可以继续拓展,

  • 比如搞一下双层缓存,例如从红薯大大的j2cache等等;
  • 再比如现在我们的evict只能清理单条或者全部清理,不支持*等表达式。这个我们可以自己单独写个工具类或者写个aop切面,再或者使用自定义注解实现清除,因为本质上就是清理redis当中的key而已,而redis是支持*操作的,这个以后看有没有机会在单独写一篇文章来介绍吧,这里就不赘述了?
    演示源码下载
  • 1
    点赞
  • 0
    评论
  • 5
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值