Spring缓存、JetCache使用学习

Spring缓存相关注解

@Cacheable注解

对于一个使用@Cacheable标注的方法,Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法。
cacheNames和value这两个属性任意使用一个都可以,且必须指定,否则会报错。它们的作用可以理解为key的前缀。

1、key和value都指定

1
@Cacheable(key = "'testKey'", value = "testValue")

生成的Redis键格式为:testValue::testKey

在这里插入图片描述

2、key和cacheNames都指定

1
@Cacheable(cacheNames = {"testNames","testNamesTwo"}, key = "'testKey'")

会生成多个Redis键,格式为:testNames::testKey
在这里插入图片描述

3、只指定value

1
@Cacheable(value = "testValue")

生成的Redis键格式为:testValue::SimpleKey []
在这里插入图片描述

4、可以取传过来的方法参数作为key
示例代码

1
2
3
4
@Cacheable(cacheNames = {"user"},key = "#id")
public Member findById(Integer id){
return memberMapper.selectByPrimaryKey(id);
}

注意:key需要在双引号里加单引号。value不需要加单引号。

@CacheEvict注解

1、清除指定key的缓存
一般来说,我们的更新操作只需要刷新缓存中某一个值,所以定义缓存的key值的方式就很重要,最好是能够唯一,因为这样可以准确的清除掉特定的缓存,而不会影响到其它缓存值。
示例代码如下:

1
@CacheEvict(key = "'testKey'", value = "testValue")

2、清除所有缓存
allEntries是boolean类型,表示是否需要清除缓存中的所有元素。默认为false,表示不需要。当指定了allEntries为true时,Spring Cache将忽略指定的key。
示例代码如下:

1
@CacheEvict(value = "testValue",allEntries=true)

@CacheConfig注解

作用在类上,相当于给该类下面所所有@Cacheable注解添加cacheNames属性。
示例代码:

1
@CacheConfig(cacheNames={"vote-achievement"})

@CachePut (不适用)

对于一个使用 @CachePut标注的方法,Spring在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
@CachePut也可以标注在类上和方法上。使用@CachePut时我们可以指定的属性跟@Cacheable是一样的。

自定义缓存过期时间

1、创建一个自定义的缓存管理器,继承自RedisCacheManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class CustomRedisCacheManager extends RedisCacheManager {

public CustomRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
super(cacheWriter, defaultCacheConfiguration);
}

/**
* 重写createRedisCache方法
* @param name 原来的name只是作为redis存储键名
* 重写的name可通过"#"拼接过期时间:
* 1. 如果没有"#"则默认不设置过期时间
* 2. 拼接的第一个"#"后面为过期时间,第二个"#"后面为时间单位
* 3. 时间单位的表示使用: d(天)、h(小时)、m(分钟)、s(秒), 默认为h(小时)
* 示例: vote-time#30#s
* @param cacheConfig
* @return
*/
@Override
protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
// 解析name,设置过期时间
if (StringUtil.isNotEmpty(name) && name.contains("#")) {
String[] split = name.split("#");
// 缓存键名
String cacheName = split[0];
// "#"后第一位是时间
int expire = Integer.parseInt(split[1]);
// 过期时间,默认为h(小时)
Duration duration = Duration.ofHours(expire);
// 根据"#"后第二位字符判断过期时间的单位,设置相应的过期时间,默认时间单位是h(小时)
if (split.length == 3) {
switch (split[2]){
case "d":
duration = Duration.ofDays(expire);
break;
case "m":
duration = Duration.ofMinutes(expire);
break;
case "s":
duration = Duration.ofSeconds(expire);
break;
default:
duration = Duration.ofHours(expire);
}
}
return super.createRedisCache(cacheName, cacheConfig.entryTtl(duration));
}
return super.createRedisCache(name, cacheConfig);
}
}

2、在redis配置类中,将上面自定义的缓存管理器注册为Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 实例化自定义的缓存管理器
* @param redisTemplate
* @return
*/
@Bean
@SuppressWarnings(value = {"unchecked", "rawtypes"})
public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(Objects.requireNonNull(redisTemplate.getConnectionFactory()));
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()));
return new CustomRedisCacheManager(redisCacheWriter, redisCacheConfiguration);
}

3、使用示例

1
@Cacheable(key = "'list'", value = "vote-achievement#20#s")

参考链接参考链接

碰到问题

Redis反序列化时报错"Could not read JSON: Cannot construct instance of java.util.ArrayList$SubList"
解决:
1、在对应的实体类中加无参构造方法(非必须)
2、ArrayList.subList方法返回的对象是一个sublist类型的视图,这个sublist类型的是ArrayList的一个内部类,不支持序列化。解决方法也是挺简单的,重新创建一个实现序列化的List,将截取后的list存入,从而实现可序列化。
示例代码如下:

1
2
3
4
// 原错误代码
bannerList = bannerList.subList(0, SLIDING_SIZE);
// 修改后的正确代码
bannerList = new ArrayList<>(bannerList.subList(0, SLIDING_SIZE));

JetCache使用学习

JetCache中Redis的键值规则为:area+name+key。area和key可以不指定。

若不指定key,默认用所有方法参数值作为Key

1
2
// 实际Redis的Key:home-page::patent-list["2019"]。其中[]为数组,拼接所有参数值
@Cached(name = "home-page::patent-list",expire =1, timeUnit = TimeUnit.HOURS)

指定key(支持Spring Spel表达式)

1
2
3
4
5
6
7
8
9
// 实际Redis的Key:home-page::patent-list2019,其中awardYear为方法的参数
@Cached(name = "home-page::patent-list",key = "#awardYear",expire =1, timeUnit = TimeUnit.HOURS)


// 实际Redis的Key:home-page::patent-list-20192022,其中awardYear、outYear为方法的参数
@Cached(name = "home-page::patent-list",key = "('-').concat(#awardYear).concat(#outYear)",expire =1, timeUnit = TimeUnit.HOURS)

// 实际Redis的Key:home-page::patent-list2019,其中args[0]为方法的第一个参数
@Cached(name = "home-page::patent-list",key = "args[0]",expire =1, timeUnit = TimeUnit.HOURS)

Spring Spel表达式学习链接