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

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

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

SpringBoot2.x 缓存---注解方式、整合RedisCluster_第1张图片

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

文章目录

  • SpringBoot2.x 缓存---注解方式、整合RedisCluster
        • やめてよ、やめてよ 、優しくしないでよ、どうしても僕には、理解ができないよ
    • 前言:
      • 注解说明:
      • 注解参数:
      • CacheManger介绍:
    • 使用本地缓存:
      • pom:
      • @EnableCaching
      • 结果:
      • 问题:
    • 整合RedisCluster配置分布式缓存:
      • pom:
      • @EnableCaching
      • 结果:
      • 问题:
    • 总结:


前言:

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

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


注解说明:

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

注解参数:

  • @EnableCaching
参数 类型 说明
proxyTargetClass boolean 是否要基于cglib生成代理去实现缓存
mode AdviceMode 选择缓存模式、默认是AdviceMode.PROXY 可以切换为 AdviceMode#ASPECTJ
order int 设置缓存管理器执行的顺序
  • @CacheConfig
参数 类型 说明
cacheNames String[] 缓存的名称,别名value
keyGenerator String 缓存key的生成器
cacheManager String 配置使用那个缓存管理器、和cacheResolver排斥
cacheResolver String 定义使用那个拦截器、和cacheManager互斥
  • @Cacheable
参数 类型 说明
value String[] 缓存的名称,别名cacheNames
cacheNames String[] 缓存的名称,别名value
key String 缓存key的值、默认是以所有的参数作为key、也可以直接配置keyGenerator或者spel表达式
keyGenerator String 缓存key的生成器
cacheManager String 配置使用那个缓存管理器、和cacheResolver排斥
cacheResolver String 定义使用那个拦截器、和cacheManager互斥
condition String 根据spel表达式来可以配置什么条件下进行缓存 默认全部缓存
unless String 和condition相反
sync boolean 是否开启同步功能、默认不开启
  • @CachePut
参数 类型 说明
value String[] 缓存的名称,别名cacheNames
cacheNames String[] 缓存的名称,别名value
key String 缓存key的值、默认是以所有的参数作为key、也可以直接配置keyGenerator或者spel表达式
keyGenerator String 缓存key的生成器
cacheManager String 配置使用那个缓存管理器、和cacheResolver排斥
cacheResolver String 定义使用那个拦截器、和cacheManager互斥
condition String 根据spel表达式来可以配置什么条件下进行缓存 默认全部缓存
unless String 和condition相反
  • @CacheEvict
参数 类型 说明
value String[] 缓存的名称,别名cacheNames
cacheNames String[] 缓存的名称,别名value
key String 缓存key的值、默认是以所有的参数作为key、也可以直接配置keyGenerator或者spel表达式
keyGenerator String 缓存key的生成器
cacheManager String 配置使用那个缓存管理器、和cacheResolver排斥
cacheResolver String 定义使用那个拦截器、和cacheManager互斥
condition String 根据spel表达式来可以配置什么条件下进行缓存 默认全部缓存
allEntries boolean 是否删除所有键的缓存 默认不删除
beforeInvocation boolean 是否在调用此方法前 删除缓存(不管方法执行成功与否都删除)
  • @Caching
参数 类型 说明
cacheable Cacheable[] 多个@Cacheable
put CachePut[] 多个@CachePut
evict CacheEvict[] 多个@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:

加上



    org.springframework.boot
    spring-boot-starter-cache

@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每次都访问了方法
    SpringBoot2.x 缓存---注解方式、整合RedisCluster_第2张图片
  3. 执行cacheEvictTest,然后执行cacheableTest,可以看到cacheableTest再次调用了方法
    SpringBoot2.x 缓存---注解方式、整合RedisCluster_第3张图片
  4. caching可以进行多个缓存的操作

问题:

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


整合RedisCluster配置分布式缓存:

pom:

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

        
        
            org.springframework.boot
            spring-boot-starter-data-redis
        

        
        
            redis.clients
            jedis
            2.10.2
        

@EnableCaching

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

@Configuration
@EnableCaching
public class RedisCacheConfig {
    @Bean
    public RedisClusterConfiguration getRedisCluster() {
        RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration();
        Set 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 redisTemplate(RedisConnectionFactory cf) {
        RedisTemplate redisTemplate = new RedisTemplate();
        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 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是支持*操作的,这个以后看有没有机会在单独写一篇文章来介绍吧,这里就不赘述了
    演示源码下载

你可能感兴趣的:(学习,java)