今日、本番環境で@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値をキャッシュに保存することは許可されていないとはっきりと書かれています。
または、RedisCacheConfiguration を設定して、NULL 値をキャッシュに保存できるようにします。
問題のコードを見てみましょう。
@Cacheable(cacheNames = "cache:getCustRange", key = "#root.args[0]['custId'] + ''")
public CustRangeVo getCustRange(Map<String, Object> map) {
return custMapper.getCustRange(map);
}
明らかに、ここでの CustRangeVo オブジェクトは空であり、キャッシュに保存するとエラーが発生します。
2.問題解決
この問題を解決する2つの方法
- 結果をnullに設定してもキャッシュされません
- 許容キャッシュをヌル値に設定します。
結果が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になるということです。
シナリオのクエリのほとんどが null で、null をキャッシュしない場合、リクエストのほとんどがデータベースにヒットすることになり、キャッシュを追加する意味がなくなります。
許容キャッシュを null 値に設定するには、RedisCacheConfiguration を設定する必要があります。
public void put(Object key, @Nullable Object value) {
Object cacheValue = preProcessCacheValue(value);
//このエラーが報告される場所には条件判定があり、値がnullであることが許されない場合にのみ報告されることがわかる。
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のソリューションはテストしていません。