今天生产环境遇到@Cacheable的一个问题,记录一下
1、发现问题
接口突然请求失败,查询日志发现有如下报错
Cache ‘cache:getCustRange’ does not allow ‘null’ values. Avoid storing null via ‘@Cacheable(unless="#result == null")’ or configure RedisCache to allow ‘null’ via RedisCacheConfiguration.
java.lang.IllegalArgumentException: Cache ‘cache:getCustRange’ does not allow ‘null’ values. Avoid storing null via ‘@Cacheable(unless="#result == null")’ or configure RedisCache to allow ‘null’ via RedisCacheConfiguration
日志提示信息说的很清楚,缓存中不允许存储null值,在@Cacheable中加上(unless="#result == null")
或者在RedisCacheConfiguration中配置成允许缓存中存储null值
下面来看一下有问题的代码
@Cacheable(cacheNames = "cache:getCustRange", key = "#root.args[0]['custId'] + ''")
public CustRangeVo getCustRange(Map<String, Object> map) {
return custMapper.getCustRange(map);
}
很明显,是这里的CustRangeVo 对象为空了,保存到缓存时就报错了
2、解决问题
两种方法来解决这个问题
设置结果为空时不缓存,直接加上unless="#result == null"就好了
@Cacheable(cacheNames = "cache:getCustRange", key = "#root.args[0]['custId'] + ''", unless="#result == null")
public CustRangeVo getCustRange(Map<String, Object> map) {
return custMapper.getCustRange(map);
}
注意,我这里的场景是99.9%会查询到数据,极端情况下对象才会为空
假如你的大部分场景查询都为空,你不缓存空的话,会导致大部分请求命中数据库,你的缓存加的就没有意义了
设置允许缓存为null值
需要配置RedisCacheConfiguration,我们看一下源码
public void put(Object key, @Nullable Object value) {
Object cacheValue = preProcessCacheValue(value);
//可以看到报这个错的地方有个条件判断,不允许value为空的情况下value为空值才会报这个错误
if (!isAllowNullValues() && cacheValue == null) {
throw new IllegalArgumentException(String.format(
"Cache '%s' does not allow 'null' values. Avoid storing null via '@Cacheable(unless=\"#result == null\")' or configure RedisCache to allow 'null' via RedisCacheConfiguration.",
name));
}
cacheWriter.put(name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue), cacheConfig.getTtl());
}
那我们继续来看一下这个isAllowNullValues()
protected RedisCache(String name, RedisCacheWriter cacheWriter, RedisCacheConfiguration cacheConfig) {
//可以看到此处allowCacheNummValues最终取的是RedisCacheConfiguration的cacheNullValues
super(cacheConfig.getAllowCacheNullValues());
Assert.notNull(name, "Name must not be null!");
Assert.notNull(cacheWriter, "CacheWriter must not be null!");
Assert.notNull(cacheConfig, "CacheConfig must not be null!");
this.name = name;
this.cacheWriter = cacheWriter;
this.cacheConfig = cacheConfig;
this.conversionService = cacheConfig.getConversionService();
}
所以你只要把RedisCacheConfiguration 的cacheNullValues设置为true,就可以缓存null值了
当然,我使用的方案是设置结果为空时不缓存,RedisCacheConfiguration 的方案我没有测试过
从文档上看应该是可行的,有兴趣的同学可以自己测试一下
如果感觉对你有些帮忙,想跟我一起学习,坚信技术改变世界,请关注Java天堂公众号,我会定期分享自己的学习成果,第一时间推送给你