Spring caching / spring repository, evict multiple keys

Saul picture Saul · May 13, 2016 · Viewed 7.6k times · Source

I have two methods to fetch an entity with two different parameters. I also have a save method that uses one of those parameters. How can I evict the entity from the cache under both fetch keys? e.g. see below:

@Cacheable  
public User getByUsername(String username);

@Cacheable  
public User getByEmail(String email);


@CacheEvict(key="#entity.username")
User save(User entity); 

In the above, a call to getByEmail will return stale date.

Answer

John Blum picture John Blum · May 14, 2016

There are several options, of course, but as usual, Spring has your back.

The easiest and most simple approach is to leverage Spring's @Caching annotation on your save method, like so...

@Caching(evict = {
  @CacheEvict(cacheNames = "Users", key="#user.name"),
  @CacheEvict(cacheNames = "Users", key="#user.email")
})
User save(User user);

For your reference, I created an example test class demonstrating this working here.

You will notice I imitated your example above using Spring's Cache Abstraction annotations on my UserRepository. In this case, my repo is backed by Pivotal GemFire, but any data store will work. I use a ConcurrentMap as my caching provider, using Spring's ConcurrentMapCacheManager, but of course, any caching provider will do.

My test case proceeds to save a new User, ensuring that the user is stored by not yet cached. The test then proceeds to exercise the query methods (findByName, findByEmail) ensuring that the user entity is cached appropriately in each case. I then remove the entity from the underlying data store and ensure that the entity is still cached. And finally, the moment of truth, I modify the entity, re-save the entity, asserting that the entity is stored by that all entries have been "evicted" from the cache.

You could also try, as another optimization, to combine the @CachePut annotation with the 2 @CacheEvict annotations in this case, which could restore the cache based on the new, updated entity, something like...

@Caching(
  evict = {
    @CacheEvict(cacheNames = "Users", key="#a0.name", beforeInvocation = true),
    @CacheEvict(cacheNames = "Users", key="#a0.email", beforeInvocation = true)
  },
  put = {
    @CachePut(cacheNames = "Users", key="#result.name"),
    @CachePut(cacheNames = "Users", key="#result.email")
  }
)
User save(User user);

NOTE: notice the user of the beforeInvocation attribute on the @CacheEvict annotations along with the @CachePuts

However, you may prefer that the entity be lazily added to the cache based on need.

Although, you would presume the entity is being frequently accessed/used since the save method on your repo was just called, and therefore rely on your underlying data store (such as GemFire) to set additional eviction (based on overflow)/expiration (based on LRU) settings, thereby better managing your system resources (e.g. memory) while still maintaining optimal application performance.

Food for thought.

Hope this helps.