8

I want to use Spring's Cache abstraction to annotate methods as @Cacheable. However, some methods are designed to take an array or collection of parameters and return a collection. For example, consider this method to find entites:

public Collection<Entity> getEntities(Collection<Long> ids)

Semantically, I need to cache Entity objects individually (keyed by id), not based on the collection of IDs as a whole. Similar to what this question is asking about.

Simple Spring Memcached supports what I want, via its ReadThroughMultiCache, but I want to use Spring's abstraction in order to support easy changing of the cache store implementation (Guava, Coherence, Hazelcast, etc), not just memcached.

What strategies exist for caching this kind of method using Spring Cache?

Community
  • 1
  • 1
E-Riz
  • 31,431
  • 9
  • 97
  • 134

2 Answers2

8

Spring's Cache Abstraction does not support this behavior out-of-the-box. However, it does not mean it is not possible; it's just a bit more work to support the desired behavior.

I wrote a small example demonstrating how a developer might accomplish this. The example uses Spring's ConcurrentMapCacheManager to demonstrate the customizations. This example will need to be adapted to your desired caching provider (e.g. Hazelcast, Coherence, etc).

In short, you need to override the CacheManager implementation's method for "decorating" the Cache. This varies from implementation to implementation. In the ConcurrentMapCacheManager, the method is createConcurrentMapCache(name:String). In Spring Data GemFire, you would override the getCache(name:String) method to decorate the Cache returned. For Guava, it would be the createGuavaCache(name:String) in the GuavaCacheManager, and so on.

Then your custom, decorated Cache implementation (perhaps/ideally, delegating to the actual Cache impl, from this) would handle caching Collections of keys and corresponding values.

There are few limitations of this approach:

  1. A cache miss is all or nothing; i.e. partial keys cached will be considered a miss if any single key is missing. Spring (OOTB) does not let you simultaneously return cache values and call the method for the diff. That would require some very extensive modifications to the Cache Abstraction that I would not recommend.

  2. My implementation is just an example so I chose not to implement the Cache.putIfAbsent(key, value) operation (here).

  3. While my implementation works, it could be made more robust.

Anyway, I hope it provides some insight in how to handle this situation properly.

The test class is self-contained (uses Spring JavaConfig) and can run without any extra dependencies (beyond Spring, JUnit and the JRE).

Cheers!

John Blum
  • 7,381
  • 1
  • 20
  • 30
  • I should also point out that if you know ahead of time that you are not planning to switch caching providers, then you can just implement the `Cache` operation in terms of the provider. For instance, if Memcached handles this using the ReadThroughMultiCache, then you could just implement the Cache.get(key) operation (and possibly, put?) interms of ReadThroughMultiCache. My solution, however, attempted to be (mostly) caching provider agnostic per your description. – John Blum Nov 12 '15 at 23:33
  • John, I appreciate the effort and idea, but limitation #1 is a deal-breaker for me. We need to cache intelligently so that items in the key list will NOT be "all-or-nothing." We need to use cached items from the list whenever they're present. – E-Riz Nov 23 '15 at 18:01
  • Right, unfortunately, off the top of my head, I cannot think of a better way to make this more intelligent other than tying yourself (to a certain degree) on on your underlying caching provider. Anyway, the framework is flexible in so much as it gives you an option for extension, to customize the behavior. However, out-of-the-box, this would be very difficult to support, especially consistently across caching providers. Open to suggestions or other ideas on such matters. – John Blum Nov 30 '15 at 18:54
  • @JohnBlum very useful – Federico Piazza Jul 09 '18 at 21:36
-2

Worked for me. Here's a link to my answer. https://stackoverflow.com/a/60992530/2891027

TL:DR

@Cacheable(cacheNames = "test", key = "#p0")
public List<String> getTestFunction(List<String> someIds) {

My example is with String but it also works with Integer and Long, and probably others.

Koroslak
  • 643
  • 1
  • 6
  • 12