How to have multiple cache manager configuration in spring cache java
Asked Answered
A

3

31

I want to have multiple spring cache managers configured in my web-application and I would be able to use different cache manager at various places in my project. Is there any way to do this.

Amicable answered 25/7, 2016 at 14:17 Comment(2)
What have you found out so far?Miyasawa
I have configured EHCacheManager in one of the module of my project, now I want to use RedisCacheManager in another module, But spring is not allowing to have 2 beans of CacheManager type in single Application context.Then I implemented CachingConfigurer in my configuration class to avoid this issue. but I end up with having only CacheManager bean on type-EHCacheManager in my ApplicationContext. But my requirement is to have both cacheManagers bean created and I should be able to use both in different modules. I heard about CompositeCacheManager, not sure will that help here or not.Thanks alotAmicable
H
46

There are several ways you can do this and the right answer depends on your usage of the cache.

You have a "main" cache manager

If you use CacheManager A for 90% of your use case and B for 10% I'd advise to create a default CacheManager for A (you'll need to specify it via a CacheConfigurerSupport extension), something like:

@Configuration
@EnableCaching
public class CacheConfig extends CachingConfigurerSupport {

    @Override
    @Bean // not strictly necessary
    public CacheManager cacheManager() { ... CacheManager A }

    @Bean
    public CacheManager bCacheManager() { ... CacheManager B }
}

Then for the 10% use case you add a CacheConfig at the top of the classes that need to use the other cache manager

@CacheConfig(cacheManager="bCacheManager")
public class MyService { /*...*/ }

If you need to use the other cache manager for only one method, you can specify that at method level as well

@Cacheable(cacheNames = "books", cacheManager = "bCacheManager")
public Book findById(long id) { /*...*/ }

More fine grained resolution

If you're not in this situation, you need a way to know which cache manager needs to be used on a case-by-case basis. You can do that based on the target type (MyService) or the name of the cache (books). You'll need to implement a CacheResolver that does that translation for you.

@Configuration
@EnableCaching
public class CacheConfig extends CachingConfigurerSupport {

    @Override
    public CacheResolver cacheResolver() { ... }
}

Check the javadoc of CacheResolver for more details. In the implementation, you may have several CacheManager instances (either as bean or not) that you'll call internally based on your logic to determine which manager should be used.

I saw in a comment that you were referring to "module". Caching is really an infrastructure matter so I'd strongly advice you to move that decision at the application level. You may tag cache as being "local" and others being "clustered". But you should probably have some kind of nomenclature for the name to make it easier. Don't choose a cache manager at the module level.

this blog post illustrates this with other examples.

Healthful answered 15/8, 2016 at 13:58 Comment(6)
Stephane, I'm following this approach to extend Spring Boot default configuration, but it seems a raw approach to my problem, can you suggest me a better way to solve my problem? thanksMiddling
watch out: in spring-boot 1.5 when extending CachingConfigurerSupport you're overriding some methods ( default keyGenerator and errorHandler) which would render your cache useless. I've used the same approach without extending that class. Just declaring the beans is enough to make it work.Proud
You don't have to override those methods. If you don't, the default KeyGenerator and ErrorHandler are going to be used.Healthful
Hello, thanks for the answer. Is there a possiblity to create a cacheManager additional to the default one create by Spring? Actually, I let Spring autoconfigure JCacheManager, with ehcache, I would keep the default one without have to redefine it. When I create my own cacheManager, the default one is not more created due to @ConditionalOnMissingBean.Mylohyoid
Please don't ask questions in a comment. The answer to that is no, if you create your own, the auto-configuration will back-off as it should. That said, you don't have to create it as a bean.Healthful
How to reuse the built-in bean configurations Spring provides? e.g. ConcurrentMapCacheManager, RedisCacheManager, etc.Cudlip
K
7

As @Stephane Nicoll explained, you have several options. I will try to give some info on custom CacheResolver. CacheResolver has one method:

Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context);

which gives context to the cacheable operation's class, method, arguments etc.

In its basic form:

public class CustomCacheResolver implements CacheResolver {

    private final CacheManager cacheManager;

    public CustomCacheResolver(CacheManager cacheManager){
        this.cacheManager = cacheManager;
    }

    @Override
    public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
        Collection<Cache> caches = getCaches(cacheManager, context);
        return caches;
    }

    private Collection<Cache> getCaches(CacheManager cacheManager, CacheOperationInvocationContext<?> context) {
        return context.getOperation().getCacheNames().stream()
            .map(cacheName -> cacheManager.getCache(cacheName))
            .filter(cache -> cache != null)
            .collect(Collectors.toList());
    }
}

Here I am using one CacheManager for brevity. But you can bind different CacheManagers to CacheResolver and make more granular selections: if class name is X, then use GuavaCacheManager, otherwise use EhCacheCacheManager.

After this step, you should register CacheResolver, (again you can bind more CacheManagers here):

@Configuration
@EnableCaching
public class CacheConfiguration extends CachingConfigurerSupport {

    @Bean
    @Override
    public CacheManager cacheManager() {
        // Desired CacheManager
    }

    @Bean
    @Override
    public CacheResolver cacheResolver() {
        return new CustomCacheResolver(cacheManager());
    }
}

And as the last step, you should specify CustomCacheResolver in one of @Cacheable, @CachePut, @CacheConfig etc. annotations.

@Cacheable(cacheResolver="cacheResolver")

You can look here for code samples.

Klepac answered 13/4, 2017 at 17:43 Comment(0)
A
0

you can write a customer CacheManager bean just like this

    @Primary
    @Bean("customerCacheManager")
    public CacheManager cacheManager(RedisTemplate<String, Object> redisTemplate) {
        Map<String, Long> expires = new HashMap<>();
        expires.put("fundShareSplit", TimeUnit.SECONDS.toSeconds(60));
        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
        cacheManager.setUsePrefix(true);
        cacheManager.setExpires(expires);
        List<String> cacheNames = Arrays.asList("fundShareSplit");
        cacheManager.setCacheNames(cacheNames);
        return cacheManager;
    }

then use this in your annoations like this

 @Cacheable(cacheManager = "customerCacheManager",value = "fundShareSplit",key="'smile:asset:fundSplit:fundId:'+#root.args[0]+'_localDate:'+#root.args[1]",unless="#result == null")
Azrael answered 10/12, 2020 at 6:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.