/collection-cacheable-for-spring

Spring cache extension for putting a whole collection of entities as single cache items

Primary LanguageJavaApache License 2.0Apache-2.0

Collection Cacheable for Spring

Build Status Quality Gate Status Code Coverage Maven Central

This library provides the @CollectionCacheable annotation extending Spring's caching mechanism, in particular @Cacheable.

@CollectionCacheable supports putting a whole collection of entities as single cache items, thus enabling an efficient integration of batch retrieval of entities. See the example usage below for a detailed explanation.

Getting started

Inside your Spring Boot application, add the following (maven) dependency:

<dependency>
    <groupId>de.qaware.tools.collection-cacheable-for-spring</groupId>
    <artifactId>collection-cacheable-for-spring-starter</artifactId>
    <version>1.0.1</version>
</dependency>

You may also use the separate artifact collection-cacheable-for-spring-api providing the annotation only with minimal dependencies.

Example usage

Suppose your entity looks as follows,

class MyEntity {
    long id;
    String value;
}

and you have defined a method to retrieve one and many values by their unique id as follows:

class MyRepository {
    @Nullable
    MyEntity findById(long id) {
        // retrieve one MyEntity from persistence layer (if existing)
    }

    // map key is MyEntity.id
    Map<Long, MyEntity> findByIds(Set<Long> ids) {
        // do efficient batch retrieve of many MyEntity's and build result map
    }
} 

Now, to efficiently implement a cache on MyRepository, you want the following to happen:

  • Whenever a call to findById occurs, either a cache hit should be returned, or the method is called putting the result in the cache unless it is null.

  • Whenever a call to findByIds occurs, the method should be called with the non-empty subset of the given ids parameter which are not in the cache. Otherwise, the cache hits are simply returned.

To illustrate the above behavior further, consider the following call sequence:

  1. findById(1) retrieves an entity from the persistence layer and fills the cache for id 1
  2. findByIds({1, 2}) finds the cache hit for id 1 and only calls findByIds({2}) as a delegate. It then fills the cache for id 2.
  3. findById(2) just retrieves the cache hit for id 2.
  4. findByIds({1, 2}) will not call anything and just returns a map built from the cache hits for id 1 and 2.

In order to implement exactly this behavior, this library is used as follows:

class MyRepository {
    @Nullable
    @Cacheable(cacheNames = "myCache", unless = "#result == null")
    MyEntity findById(long id) {
        // retrieve one MyEntity from persistence layer (if existing)
    }

    @CollectionCacheable(cacheNames = "myCache")
    Map<Long, MyEntity> findByIds(Set<Long> ids) {
        // do efficient batch retrieve of many MyEntity's and build result map
    }
} 

See this test repository for a completely worked out example.

Advanced usage

Additional "findAll" method

If your repository also provides a "findAll"-like method without any arguments, you can integrate this as well into your cache as follows:

class MyRepository {

    @CollectionCacheable(cacheNames = "myCache")
        // map key is MyEntity.id
    Map<Long, MyEntity> findAll() {
        // do efficient batch retrieve of many MyEntity's and build result map
    }
} 

The library assumes that such methods do not have any arguments. Note that the return value must still be a Map, otherwise the library is unable to determine the cache id.

Also consider null as cache hit

Under some circumstances, it is also desirable to cache also null results. This must be explicitly enabled via the putNull flag on batch retrieval as follows:

class MyRepository {
    @CollectionCacheable(cacheNames = "myCache", putNull = true)
    Map<Long, MyEntity> findByIds(Set<Long> ids) {
        // do efficient batch retrieve of many MyEntity's and build result map
    }
} 

Contributing

Please report issues or feature requests.

Also see this Spring issue here. As of now, integration into Spring is not ideal, so this library might break with future Spring releases.