Reputation: 5822
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:
getJob("123", false)
=> job v1 is returned (fetched from the cache if present)getJob("123", true)
=> job v2 is returned (updated version, fetched from db)getJob("123", false)
=> job v2 is returned (updated version, fetched from cache)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
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
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