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.
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.
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 isnull
. -
Whenever a call to
findByIds
occurs, the method should be called with the non-empty subset of the givenids
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:
findById(1)
retrieves an entity from the persistence layer and fills the cache for id1
findByIds({1, 2})
finds the cache hit for id1
and only callsfindByIds({2})
as a delegate. It then fills the cache for id2
.findById(2)
just retrieves the cache hit for id2
.findByIds({1, 2})
will not call anything and just returns a map built from the cache hits for id1
and2
.
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.
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.
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
}
}
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.