javaspring-bootcachingcaffeine-cache

Cache With Complex Conditional Eviction


I'm trying to use caffeine and spring-boot-starter-cache to implement the following caching logic:

  1. If expiration time has passed and condition (That requires computation and I/O) is evaluated to TRUE then force fetch the data and update cache.
  2. If expiration time has passed and condition (That requires computation and I/O) is evaluated to FALSE then don't invalidate the cache data and retrieve the value from cache.
  3. If expiration time has not passed then retrieve the value from cache.

I worked according to this guide: https://www.baeldung.com/spring-boot-caffeine-cache

I tried all sort of approaches using @CachePut, @CacheEvict and @Cacheable on the getter method of the object I;m caching but the core issue is that I need to condition the eviction with both an expiration time and another logic but these annotations cannot control whether to evict or not... Perhaps this can be done using a Scheduler?


Solution

  • I think you are looking for refreshAfterWrite and override CacheLoader.reload(K, V). Here is the post that explain the details: https://github.com/ben-manes/caffeine/wiki/Refresh

    Implementation for your case would be something like:

    @Log4j2
    @Configuration
    public class CacheConfig {
    
        @Bean
        public Cache<String,Item> caffeineConfig() {
            return Caffeine.newBuilder()
                .refreshAfterWrite(10, TimeUnit.SECONDS)
                .build(new ConditionalCacheLoader<>(this::shouldReload, this::load));
        }
    
        private Item load(String key){
            //load the item
            return null;
        }
    
        private boolean shouldReload(Item oldValue){
            //your condition logic here
            return true;
        }
    
    
        private static class Item{
             // the item value can contain any data
        }
    
        private static class ConditionalCacheLoader<K,V> implements CacheLoader<K,V>{
            private final Predicate<V> shouldReload;
    
            private final Function<K,V> load;
    
            protected ConditionalCacheLoader(Predicate<V> shouldReload, Function<K, V> load) {
                this.shouldReload = shouldReload;
                this.load = load;
            }
    
            @Override
            public V load(K key) throws Exception{
                return load.apply(key);
            }
    
            @Override
            public V reload(K key, V oldValue) throws Exception {
                if (shouldReload.test(oldValue)){
                    return load(key);
                }else {
                    return oldValue;
                }
            }
        }
    }