SpringBoot在使用@Cacheable缓存对象为空时遇到的坑

今天生产环境遇到@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、解决问题

两种方法来解决这个问题

  • 设置结果为空时不缓存
  • 设置允许缓存为null值

设置结果为空时不缓存,直接加上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天堂公众号,我会定期分享自己的学习成果,第一时间推送给你

SpringBoot在使用@Cacheable缓存对象为空时遇到的坑_第1张图片

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