Spring Cache with collection of items/entities

Charbel picture Charbel · Jun 13, 2017 · Viewed 21.9k times · Source

I am using Spring Cache, where I pass in a collection of keys, and the return is a list of entities. I would like to have the caching framework understand that each element in the return list is to be cached with the corresponding code. At the moment, it seems that the key is the whole list, and if I am missing a key in the subsequent call, it'll try to reload the whole collection again.

@Override
@Cacheable(value = "countries")
public List<Country> getAll(List<String>codes) {
    return countryDao.findAllInCodes(codes);
}

another possibility is that the return is a map, similarly I would like the cache to be intelligent enough to only query for items that were never queried before, also to cache them each item with its key.

@Override
@Cacheable(value = "countries")
public Map<String,Country> getAllByCode(List<String>codes) {
    return countryDao.findAllInCodes(codes);
}

Suppose the country class looks like this:

class Country{
  String code; 
  String fullName;
  long id;

... // getters setters constructurs etc.. 
}

Is this possible with Spring Cache?

Answer

John Blum picture John Blum · Jun 15, 2017

In fact, it is possible, even with Spring's Caching Abstraction, but not out-of-the-box (OOTB). Essentially, you must customize Spring's caching infrastructure (Explained further below)

By default, Spring's caching infrastructure uses the entire @Cacheable method parameter arguments as the cache "key", as explained here. Of course you can also customize the key resolution using either a SpEL Expression or with a custom KeyGenerator implementation, as explained here.

Still, that does not break up the collection or array of parameter arguments along with the @Cacheable method's return value into individual cache entries (i.e. key/value pairs based on the array/collection or Map).

For that, you need a custom implementation of Spring's CacheManager (dependent on your caching strategy/provider) and Cache interfaces.

NOTE: Ironically, this will be the 3rd time I have answered nearly the same question, first here, then here and now here, :-). Anyway...

I have updated/cleaned up my example (a bit) for this posting.

Notice that my example extends and customizes the ConcurrentMapCacheManager provided in the Spring Framework itself.

Theoretically, you could extend/customize any CacheManager implementation, like Redis's in Spring Data Redis, here (source), or Pivotal GemFire's CacheManager in Spring Data GemFire, here (source). The open source version of Pivotal GemFire is Apache Geode, which has a corresponding Spring Data Geode project, (source for CacheManager in Spring Data Geode, which is basically identical to SD GemFire). Of course, you can apply this technique to other caching providers... Hazelcast, Ehcache, etc.

However, the real guts of the work is handled by the custom implementation (or mores specifically, the base class) of Spring's Cache interface.

Anyway, hopefully from my example, you will be able to figure out what you need to do in your application to satisfy your application's caching requirements.

Additionally, you can apply the same approach to handling Maps, but I will leave that as an exercise for you, ;-).

Hope this helps!

Cheers, John