Reputation: 71
I have a requirement to use old/stale cache value if cache refresh is failed. I have gone through the many examples on internet and decided to try with caffeine cache to implement this requirements(more specifically using refreshAfterWrite feature). I am able to cache the data using caffeine but not able to get old/stale value when cache refresh(rest api is failed) is failed. I have used SimpleCacheManager(org.springframework.cache.support.SimpleCacheManager) and CaffeineCache.
**simpleCacheManager bean code** :
<bean id="simpleCacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches" ref="caffeineCaches" />
</bean>
**caffeine cache** :
@Bean
public Collection<? extends org.springframework.cache.Cache> caffeineCaches(){
Collection<CaffeineCache> caffeineCaches = new ArrayList<>();
caffeineCaches.add(new CaffeineCache("pbdBankRegistry",
Caffeine.newBuilder()
.refreshAfterWrite(2, TimeUnit.MINUTES)
.initialCapacity(1)
.maximumSize(2)
.recordStats()
.build(new CustomCacheLoader()),false));
return caffeineCaches;
}
**CacheLoader Code :**
public class CustomCacheLoader implements CacheLoader<Object, Object> {
@Nullable
@Override
public Object load(Object o) throws Exception {
return null;
}
@Override
public Map loadAll(Set keys) throws Exception {
return CacheLoader.super.loadAll(keys);
}
@Override
public CompletableFuture asyncLoad(Object key, Executor executor) throws Exception {
return CacheLoader.super.asyncLoad(key, executor);
}
@Override
public CompletableFuture<? extends Map> asyncLoadAll(Set keys, Executor executor) throws Exception {
return CacheLoader.super.asyncLoadAll(keys, executor);
}
@Nullable
@Override
public Object reload(Object key, Object oldValue) throws Exception {
return CacheLoader.super.reload(key, oldValue);
}
@Override
public CompletableFuture asyncReload(Object key, Object oldValue, Executor executor) throws Exception {
return CacheLoader.super.asyncReload(key, oldValue, executor);
}
I think i have to call my api from reload method or from build method of Caffeine. but i am not sure. I am new with the cache so here need your help to implement this code.
Note : My application is on spring 4.3.3.RELEASE version.
Upvotes: 2
Views: 1826
Reputation: 1982
Something like the below worked for me in a similar case: rather than supplying a Function to .build
as per the examples at https://github.com/ben-manes/caffeine/wiki/Refresh, pass in an object that implements CacheLoader
, and override both the load
and reload
methods.
/**
* @see https://www.javadoc.io/doc/com.github.ben-manes.caffeine/caffeine/latest/com.github.benmanes.caffeine/com/github/benmanes/caffeine/cache/CacheLoader.html#reload(K,V)
*/
class CacheLoader_keepOldIf<K,V> implements CacheLoader<K,V> {
private Function<K,V> loadFn;
private Function<V,Boolean> testNewFn;
CacheLoader_keepOldIf(
Function<V,Boolean> testNewFn,
Function<K,V> loadFn
){
this.loadFn = loadFn;
this.testNewFn = testNewFn;
}
@Override
public @Nullable V reload(K key, V oldValue) throws Exception {
V newValue = loadFn.apply(key);
return testNewFn.apply(newValue) ? oldValue : newValue;
}
@Override
public @Nullable V load(K key) throws Exception {
return loadFn.apply(key);
}
}
LoadingCache<String, Object> myCache = Caffeine.newBuilder()
.refreshAfterWrite(5, TimeUnit.SECONDS)
.build(new CacheLoader_keepOldIf<String,Object>(
newValue -> newValue==null,
key -> someExpensiveCallReturningNullOnFailure()
))
;
This code seems needlessly verbose just to override the reload
method: some of this is doubtless my fault, and some possibly Java's. But it did the job: if my DB call fails, it returns null, and the previously cached value is used. It would presumably also be possible to specify a default value in the load
method in case the first call to populate the cache fails.
(As per Ben Manes' comment on the question, I think the override of load
in the example in the question should return something instead of null to fix the original issue).
Upvotes: 0