fikkatra
fikkatra

Reputation: 5822

Spring Cache: force update cache based on condition

I have a list of jobs that need to be cached (by id). In some cases however, it is important to have the latest version of a job, and the cache needs to be bypassed (force update). When that happens, the newly fetched job should be placed in the cache.

I implemented it like this:

@Cacheable(cacheNames = "jobs", key = "#id", condition = "!#forceRefresh", sync = true)
public Job getJob(String id, boolean forceRefresh) {
    // expensive fetch
}

Desired behavior:

In reality, the last call getJob("123", false) returns job v1, the stale version. It seems like the second call (forced update) does not update the value in the cache.

How can I achieve the correct behavior here?

Cache config (using Caffeine):

CaffeineCache jobs = new CaffeineCache("jobs", Caffeine.newBuilder()
        .expireAfterWrite(1, TimeUnit.MINUTES)
        .maximumSize(100)
        .build());

Upvotes: 3

Views: 5075

Answers (2)

fikkatra
fikkatra

Reputation: 5822

In case forceRefresh is true, Spring cache won't be activated because of the condition condition = "!#forceRefresh". Hence, the cache value won't be updated.

You need to explicitly tell Spring to update the cache value with @CachePut in case forceRefresh is true:

@Caching(
    cacheable = {@Cacheable(cacheNames = "jobs", key = "#id", condition = "!#forceRefresh")},
    put = {@CachePut(cacheNames = "jobs", key = "#id", condition = "#forceRefresh")}
)
public Job getJob(String id, boolean forceRefresh) {
    // expensive fetch
}

Upvotes: 6

Chris Savory
Chris Savory

Reputation: 2755

I have run into this problem before and solved it a couple of ways. The easiest way to solve it is to all your updates to Job done through this same JobService you are using. If that is the case you just do this:

    @Caching(evict = {
            @CacheEvict(value = "jobs", key = "#job.id") })
    public void updateJob( Job job ) {

This way when the Job is updated it will be evicted in the cache and your next call to getJob will pull a fresh one.

The next way is if you have some other process updating your database and updateJob is not used to update the actual source. At that point, I have implemented it where I built a Quartz Job to refresh/update my cache entry on a schedule (i.e. every 15 mins). It looks something like this.

    @Autowired
    CacheManager cacheManager;

    public void refreshJobs() {
        Cache cache = cacheManager.getCache( "jobs" );

        for ( Job job : getJobs() ) {
            cache.put( job.getId(), job );
        }
    }

You might get some stale Jobs with that solution, but you know it is being refreshed every 5, 10 or 15 mins.

Upvotes: 2

Related Questions