当前位置: 首页 > 工具软件 > CacheManager > 使用案例 >

springboot2.x +RedisCacheManager + CacheManager +@Cacheable 实现注解化缓存管理

子车安和
2023-12-01


实际业务开发中,免不了会使用redis作为缓存,加快接口响应速度。一个典型的场景:前端请求到后端服务时,后端服务先查询redis缓存,如果查到则使用缓存数据,否则再查mysql数据库,完后放到redis中。

上面场景一般的实现过程,一般的都是:

//1.查询redis
//2.判断redis结果是否为空
//3.如果为空,则继续查mysql数据库
//4.mysql的结果放到redis中
//5.返回结果

上述步骤是比较繁琐的,也不优雅,我们可以使 用spring的@Cacheable、@CacheEvict、@CachePut 等注解来更优雅的实现上述过程。

一. 环境准备

1.1 引入redis starter依赖

因为是基于redis的缓存,因此要引入redis的依赖,外加redis基础配置,这个步骤就不详细展开了。

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

1.2 配置CacheManager

需要特别注意,此配置是对于springboot2.x版本和1.x,配置是完全不同的

1.2.1 springboot2.x

@EnableCaching
@Configuration
public class RedisConfig {
	
    @Bean("10m")
    public CacheManager cacheManager10m(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration
                .defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(10))
                //替换原来的cache name
//                .prefixKeysWith("QQQQ")
                .computePrefixWith(cacheName -> "yourappname".concat(":").concat(cacheName).concat(":"))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

        return RedisCacheManager
                .builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
                .cacheDefaults(redisCacheConfiguration).build();
    }
    @Primary
    @Bean("20m")
    public CacheManager cacheManager20m(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration
                .defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(20))
                //替换原来的cache name
//                .prefixKeysWith("QQQQ")
                .computePrefixWith(cacheName -> "ATTENDANCE".concat(":").concat(cacheName).concat(":"))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

        return RedisCacheManager
                .builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
                .cacheDefaults(redisCacheConfiguration).build();
    }
}

配置项说明:

  1. 上面配置了两个CacheManager,分别是@Bean(“10m”)和@Bean(“20m”),他们配置的key的过期时间不同,如果还需要其他的过期时间,可以继续copy;
  2. @Primary :如果配置了超过1个CacheManager,最好指定一个优先使用的,因为如果在业务中使用CacheManager时忘记指定具体的CacheManager,那么会启动报错。

暂且先说明这两个点,具体的每一个配置项的作用,放在下文结合具体的例子说明。

1.2.2 springboot1.x

@Bean
public CacheManager cacheManager(RedisTemplate redisTemplate) {
    RedisCacheManager cacheManager= new RedisCacheManager(redisTemplate);
    cacheManager.setDefaultExpiration(60);
    Map<String,Long> expiresMap=new HashMap<>();
    expiresMap.put("Product",5L);
    cacheManager.setExpires(expiresMap);
    return cacheManager;
}

1.x版本的配置,网上有比较多的资料,这里不展开说明了。

二. 具体使用

下面介绍下@Cacheable、@CacheEvict、@CachePut的使用姿势。

2.1 @Cacheable

定义一个接口方法:

 @Cacheable(value = "testCacheable", key = "#p0 +'_'+ #p1", cacheManager = "10m" ,unless = "#result==null")
 @Override
 public String testCacheable(String a, String b) {
     System.out.println("start testCacheable");
     return "testCacheableEnd";
 }

  @Test
  public void testRedis() {
      String s = workScheduleApplicationService.testRedis("11", "22");
      System.out.println(s);
  }

过程说明:

当testCacheable方法被调用时,spring会谁先查询redis中有没有"testCacheable:11_22"这个key,有的话,直接取这个key的vlaue值返回,testCacheable方法的内容不会被执行;

如果没有"testCacheable:11_22"这个key,那么testCacheable方法体会被执行,并将执行完的结果"testCacheableEnd"缓存到redis中,key就是"testCacheable:11_22"。

@Cacheable参数说明:

  1. value = “testCacheable” ,指定cache name,必须得指定,也是key的一部分;

  2. key = “#p0 +’_’+ #p1” :缓存的key,#p0是取方法的第一个入参值,#p1是取第二个入参值;如果参数是个对象,还可以使用如下方式取值:

    @Cacheable(value="users", key="#p0.id")
     public User find(User user) {
        return null;
     }
    
  3. cacheManager = “10m” :指定具体的cacheManager,其中10m是cacheManager的名字,也就是上面配置类中的 @Bean(“10m”)。如果不指定cacheManager参数,会默认使用@Primary修饰的bean;如果有多个cacheManager定义,但是没有@Primary修饰,会启动报错。

  4. unless = “#result==null” :只有方法体返回的结果不是null时,才进行缓存。因为null进行缓存没有意义。如果result是个对象,还可以判断result中的属性,比如#result.name == null.

2.2 @CachePut

还是上文中的方法:

@CachePut(value = "testCacheable", key = "#p0 +'_'+ #p1", cacheManager = "10m")
@Override
public String testCacheable(String a, String b) {
    System.out.println("start testCacheable");
    return "testCacheableEnd";
}

@CachePut的作用是,每次方法执行完,都将结果放到redis中。每次方法调用都不会查询redis,而是直接执行方法体。

@CachePut和@Cacheable 的参数基本一样,这里不单独进行说明。

@CachePut使用场景:

每次查询不使用redis,查询完放到redis,这有什么用呢。一个典型的场景是进行数据兜底

比如feign的rpc调用:

@FeignClient(value = "xxx", url = "xxx", fallbackFactory = XXClientFallbackFactory.class)
public interface CampClient {

	@CachePut(value = "testCacheable", key = "#p0 +'_'+ #p1", cacheManager = "10m")
	@RequestMapping(value = "/xx/xx/xx/xxx", method = RequestMethod.GET)
	FeignResult testCacheable(String a, String b)
}  

上面我们在feign的接口方法上加上了@CachePut注解,那么每次feign调用成功后,都会将结果缓存到redis中。

那么,当feign接口调用失败,进入到降级方法中时,就会先查询redis,从而将上次成功缓存的结果返回,从而达到数据兜底的效果。

@Cacheable(value = "testCacheable", key = "#p0 +'_'+ #p1", cacheManager = "10m" ,unless = "#result==null")
 @Override
 public FeignResult testCacheable(String a, String b) {
 	 log.error("testCacheable 进入降级 ");
     return null;
 }

2.3 @CacheEvict

@CacheEvict是用来标注在需要清除缓存元素的方法或类上的。

当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。

@CacheEvict可以指定的属性有value、key、condition、allEntries、beforeInvocation。

对于@Cacheable、@CacheEvict、@CachePut的使用姿势,本文只是大致介绍,更多的使用方式,网上文章较多。

三. RedisCacheConfiguration参数说明

文章开始配置CacheManager的时候,使用了RedisCacheConfiguration配置类,下面针对其常用配置参数做简单介绍。

RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration
                .defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(10))
                //替换原来的cache name
//                .prefixKeysWith("QQQQ")
                .computePrefixWith(cacheName -> "yourappname".concat(":").concat(cacheName).concat(":"))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
                

entryTtl

指定key的过期时间,比如Duration.ofMinutes(10),也就是10分钟,设置此参数后,通过这个CacheManager存入redis的key,过期时间都是10分钟。

如果不想设置过期时间,需要key永久你不过期,那么此参数设置为Duration.ZERO。

prefixKeysWith

设置key的前缀,比如本文的例子中:

//入参数 为  11,22
@Cacheable(value = "testCacheable", key = "#p0 +'_'+ #p1", cacheManager = "10m" )

如果不设置prefixKeysWith参数,上述产生的key是:testCacheable:11_22,
如果配置了prefixKeysWith,比如随便配置为“QQQQ”,那么产生的key是: QQQQ:11_22

computePrefixWith
computePrefixWith与prefixKeysWith参数类似,不同的是prefixKeysWith是取代了@Cacheable的value参数值,比如本文例子是“testCacheable”,
而computePrefixWith可以针对value值进行一番计算,比如如下配置:

.computePrefixWith(cacheName -> "yourappname".concat(":").concat(cacheName).concat(":"))

上述配置产生的key为:yourappname:testCacheable:11_22 .

 类似资料: