'Spring Cache Caffeine bulk retrieval
Is it possible to use Caffeine's CacheLoader::loadAll
with @Cacheable
annotated method with collection parameter, like
@Cacheable(cacheNames = "exampleCache", cacheManager="exampleCacheManager", keyGenerator = "complexKeyGenerator")
List<String> getItems(List<String> keys, String commonForEveryKey) {
return ...
}
@Component
class ComplexKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
return ((List<String>)params[0]).stream()
.map(item -> new ComplexKey(item, (String) params[1]))
.collect(Collectors.toList());
}
}
@Data
@AllArgsConstructor
class ComplexKey {
String key;
String commonGuy;
}
class CustomCacheLoader implements CacheLoader<ComplexKey, String> {
@Override
public @Nullable String load(@NonNull ComplexKey key) throws Exception {
return loadAll(List.of(key)).get(key);
}
@Override
public @NonNull Map<@NonNull ComplexKey, @NonNull String> loadAll(@NonNull Iterable<? extends @NonNull ComplexKey> keys)
throws Exception {
return ...
}
}
@Bean
CacheManager exampleCacheManager(LoadingCache exampleCache) {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.registerCustomCache("exampleCache", exampleCache());
return cacheManager;
}
@Bean
Cache<Object, Object> exampleCache() {
return Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(1, TimeUnit.HOURS)
.recordStats()
.build(new CustomCacheLoader());
}
Looks like Spring Cache invokes CustomCacheLoader::load
instead of CustomCacheLoader::loadAll
and fails on ClassCastException
since it cannot cast collection of keys into single key.
What else should I configure to make it work?
Solution 1:[1]
Unfortunately Spring doesn't support retrieving of a collection of cached items by a collection of keys via @Cacheable
mechanism.
Here's an issue on that: https://github.com/spring-projects/spring-framework/issues/23221
One option to achieve this is to use a custom library ( https://github.com/qaware/collection-cacheable-for-spring) that provides a @CollectionCacheable
annotation:
@CollectionCacheable(cacheNames = "myCache")
Map<Long, MyEntity> findByIds(Collection<Long> ids) {
// do efficient batch retrieve of many MyEntity's and build result map
}
If you'd really like to stick with the code you have you could generalize it to something similar:
@Component
class ComplexKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
if (params.length < 2 || !(params[0] instanceof Collection && params[1] instanceof String)) {
return SimpleKeyGenerator.generateKey(params);
}
return ((Collection<String>) params[0]).stream()
.map(item -> new ComplexKey(item, (String) params[1]))
.collect(Collectors.toList());
}
}
class CustomCacheLoader implements CacheLoader<Object, Object> {
@Override
public Object load(Object key) throws Exception {
final Collection<Object> keys = (key instanceof Collection) ?
((Collection<Object>) key) : Collections.singletonList(key);
final Collection<Object> values = new ArrayList<>(loadAll(keys).values());
return values;
}
@Override
public Map<Object, Object> loadAll(Iterable<?> keys) throws Exception {
...
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | dekkard |