Reputation: 55555
I'm looking to add several distinct LoadingCache
's to a Spring CacheManager
, however I don't see how this is possible using CaffeineCacheManager
. It appears that only a single loader is possible for refreshing content, however I need separate loaders for each cache. Is it possible to add multiple loading caches to a Spring cache manager? If so, then how?
CaffeineCacheManager cacheManage = new CaffeineCacheManager();
LoadingCache<String, Optional<Edition>> loadingCache1 =
Caffeine.newBuilder()
.maximumSize(150)
.refreshAfterWrite(5, TimeUnit.MINUTES)
.build(test -> this.testRepo.find(test));
LoadingCache<String, Optional<Edition>> loadingCache2 =
Caffeine.newBuilder()
.maximumSize(150)
.refreshAfterWrite(5, TimeUnit.MINUTES)
.build(test2 -> this.testRepo.find2(test2));
// How do I add to cache manager, and specify a name?
Upvotes: 16
Views: 14834
Reputation: 23
It's mandatory to build your custom Caffeine Cache using a com.github.benmanes.caffeine.cache.Ticker
.
This is a working example tested with Java 17, Spring Boot 2.7.7 and Caffeine 3.1.6 where we configure a cacheOne with an expiration time of 60 seconds and a cacheTwo which expires after one hour or 3600 seconds:
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManagerTicker(Ticker ticker) {
var cacheManager = new SimpleCacheManager();
cacheManager.setCaches(List.of(
this.buildCache("cacheOne", ticker, 1, 60, TimeUnit.SECONDS),
this.buildCache("cacheTwo", ticker, 1, 3600, TimeUnit.SECONDS)
));
return cacheManager;
}
private CaffeineCache buildCache(String cacheName, Ticker ticker,
int maxSize, int expireAfterWrite, TimeUnit timeUnit) {
Caffeine<Object, Object> cacheBuilder = Caffeine.newBuilder();
if (expireAfterWrite > 0) {
cacheBuilder.expireAfterWrite(expireAfterWrite, timeUnit);
}
if (maxSize > 0) {
cacheBuilder.maximumSize(maxSize);
}
cacheBuilder.ticker(ticker);
return new CaffeineCache(cacheName, cacheBuilder.build());
}
@Bean
public Ticker ticker() {
return Ticker.systemTicker();
}
}
This example was adapted from Define multiple caches configurations with Spring and Caffeine where Ben Manes points out there is an adapter called Coffee Boots which features the requested behaviour: https://github.com/stepio/coffee-boots
Upvotes: 1
Reputation: 557
Thanks for @rado, this is improved version of his answer. This way we can configure the cache from application properties directly
cache:
specs:
big-cache:
expire-after: WRITE
timeout: 2h
max-size: 1000
long-cache:
expire-after: ACCESS
timeout: 30d
max-size: 100
We need a cache properties for this
@Data
@EnableConfigurationProperties
@Configuration
@ConfigurationProperties(prefix = "cache")
public class CacheProperties {
private static final int DEFAULT_CACHE_SIZE = 100;
private Map<String, CacheSpec> specs = new HashMap<>();
@Data
public static class CacheSpec {
private Duration timeout;
private Integer maxSize = DEFAULT_CACHE_SIZE;
private ExpireAfter expireAfter = ExpireAfter.WRITE;
}
enum ExpireAfter { WRITE, ACCESS }
}
And then we can configure directly from external config file
@EnableCaching
@Configuration
@RequiredArgsConstructor
public class CacheConfiguration {
private final CacheProperties cacheProperties;
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager();
Map<String, CacheProperties.CacheSpec> specs = cacheProperties.getSpecs();
specs.keySet().forEach(cacheName -> {
CacheProperties.CacheSpec spec = specs.get(cacheName);
manager.registerCustomCache(cacheName, buildCache(spec));
});
// to avoid dynamic caches and be sure each name is assigned
// throws error when tries to use a new cache
manager.setCacheNames(Collections.emptyList());
return manager;
}
private Cache<Object, Object> buildCache(CacheProperties.CacheSpec cacheSpec) {
if (cacheSpec.getExpireAfter() == CacheProperties.ExpireAfter.ACCESS) {
return Caffeine.newBuilder()
.expireAfterAccess(cacheSpec.getTimeout())
.build();
}
return Caffeine.newBuilder()
.expireAfterWrite(cacheSpec.getTimeout())
.build();
}
}
Now you can use the cache with using cache name
@Cacheable(cacheNames = "big-cache", key = "{#key}", unless="#result == null")
public Object findByKeyFromBigCache(String key) {
// create the required object and return
}
@Cacheable(cacheNames = "long-cache", key = "{#key}", unless="#result == null")
public Object findByKeyFromLongCache(String key) {
// create the required object and return
}
Upvotes: 1
Reputation: 6792
Having this class will allow you to use @Cacheable("cacheA")
where you want as normal:
@EnableCaching
@Configuration
public class CacheConfiguration {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager();
manager.registerCustomCache("cacheA", defaultCache());
manager.registerCustomCache("cacheB", bigCache());
manager.registerCustomCache("cacheC", longCache());
// to avoid dynamic caches and be sure each name is assigned to a specific config (dynamic = false)
// throws error when tries to use a new cache
manager.setCacheNames(Collections.emptyList());
return manager;
}
private static Cache<Object, Object> defaultCache() {
return Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build();
}
private static Cache<Object, Object> bigCache() {
return Caffeine.newBuilder()
.maximumSize(5000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build();
}
private static Cache<Object, Object> longCache() {
return Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(1, TimeUnit.HOURS)
.build();
}
}
Upvotes: 3
Reputation: 33091
Yes it is possible. Since you need to fine tune every cache, you are probably better at defining them yourself. Back to your example, the next step would be:
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(
new CaffeineCache("first", loadingCache1),
new CaffeineCache("second", loadingCache2)));
And then you can use that as usual, e.g.
@Cacheable("first")
public Foo load(String id) { ... }
If you are using Spring Boot, you can just expose the individual cache as beans (so org.springframework.cache.Cache
implementations) and we'll detect them and create a SimpleCacheManager
automatically for you.
Note that this strategy allows you to use the cache abstraction with different implementations. first
could be a caffeine cache and second
a cache from another provider.
Upvotes: 32