I am facing a strange issue - I have hazelcast and redis in my project. Suddenly all @Cacheable
annotations are putting entries only to hazelcast cache, even if the particular cache name is configured via redis cache builder:
@Bean
fun redisCacheManagerBuilderCustomizer(): RedisCacheManagerBuilderCustomizer? {
return RedisCacheManagerBuilderCustomizer { builder: RedisCacheManagerBuilder ->
builder
.withCacheConfiguration(
MY_CACHE,
RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofDays(3))
)
}
}
Using cache:
@Cacheable(cacheNames = [CacheConfig.MY_CACHE])
@Cacheable(value= [CacheConfig.MY_CACHE])
Both does not work and forwards requests to hazelcast only. How to solve this? Using different cacheManager?
Typically, only 1 caching provider is in use to cache data, such as in the service or data access tier of your Spring [Boot] application using Spring's Cache Abstraction and infrastructure components, such as the CacheManager
and caching annotations.
When multiple caching providers (e.g. Hazelcast and Redis) are on the classpath of your Spring Boot application, then it might be necessary to declare which caching provider (e.g. Redis) you want to [solely] use for caching purposes. With this arrangement, Spring Boot allows you to declare your intentions using the spring.cache.type
property as explained in the ref doc, here (see first Tip). Valid values of this property are defined by the enumerated values in the CacheType
enum.
However, if you want to cache data using multiple caching providers at once, then you need to explicitly declare your intentions using this approach as well.
DISCLAIMER: It has been awhile since I have traced through Spring Boot auto-configuration where caching is concerned, and how it specifically handles multiple caching providers on the application classpath, especially when a specific caching provider has not been declared, such as by explicitly declaring the
spring.cache.type
property. However, and again, this may actually be your intention, to use multiple caching providers in a single@Cacheable
(or@CachePut
) service or data access operation. If so, continue reading...
To do so, you typically use 1 of 2 approaches. These approaches are loosely described in the core Spring Framework's ref doc, here.
1 approach is to declare the cacheNames of the caches from each caching provider along with the CacheManager
, like so:
@Service
class CustomerService {
@Cacheable(cacheNames = { "cacheOne", "cacheTwo" }, cacheManager="compositeCacheManager")
public Customer findBy(String name) {
// ...
}
}
In this case, "cacheOne" would be the name of the Cache
managed by caching provider one (e.g. Redis), and "cacheTwo" would be the name of the Cache
managed by caching provider two (i.e. "Hazelcast").
DISCLAIMER: You'd have to play around, but it might be possible to simply declare a single
Cache
name here (e.g. "Customers"), where the caches (or cache data structures in each caching provider) are named the same, and it would still work. I am not certain, but it seems logical this would work as well.
The key (no pun intended) to this example, however, is the declaration of the CacheManager
using the cacheManager
attribute of the @Cacheable
annotation. As you know, the CacheManager
is the Spring SPI infrastructure component used to find and manage Cache
objects (caches from the caching providers) used for caching purposes in your Spring managed beans (such as CustomerService
).
I named this CacheManager
deliberately, "compositeCacheManager". Spring's Cache Abstraction provides the CompositeCacheManager
implementation, which as the name suggests, composes multiple CacheManagers
for use in single cache operation.
Therefore, you could do the following in you Spring [Boot] application configuration:
@Configuration
class MyCachingConfiguration {
@Bean
RedisCacheManager cacheManager() {
// ...
}
@Bean
HazelcastCacheManager hazelcastCacheManager() {
// ...
}
@Bean
CompositeCacheManager compositeCacheManager(RedisCacheManager redis, HazelcastCacheManager hazelcast) {
return new CompositeCacheManager(redis, hazelcast);
}
}
NOTE: Notice the
RedisCacheManager
is the "default"CacheManager
declaration and cache provider (implementation) used when no cache provider is explicitly declared in a caching operation, since the bean name is "cacheManager".
Alternatively, and perhaps more easily, you can choose to implement the CacheResolver
interface instead. The Javadoc is rather self-explanatory. Be aware of the Thread-safety concerns.
In this case, you would simply declare a CacheResolver
implementation in your configuration, like so:
@Configuration
class MyCachingConfiguration {
@Bean
CacheResolver customCacheResolver() {
// return your custom CacheResolver implementation
}
}
Then in your application service components (beans), you would do:
@Service
class CustomerService {
@Cacheable(cacheNames = "Customers", cacheResolver="customCacheResolver")
public Customer findBy(String name) {
// ...
}
}
DISCLAIMER: I have not tested either approach I presented above here, but I feel reasonably confident this should work as expected. It may need some slight modifications, but should generally be the approach(es) you should follow.
If you have any troubles, please post back in the comments and I will try to follow up.