1

In Spring Boot, I would like to use one cache manager defined as bean plus Redis cache defined in application.yaml. But after define cache manager like bean the other one in application.yaml is ignored.

 @Bean
public CacheManager cacheManager() {
    return new ConcurrentMapCacheManager(LDAP_CACHE_NAME) {

        @Override
        protected Cache createConcurrentMapCache(final String name) {
            return new ConcurrentMapCache(name, CacheBuilder.newBuilder().expireAfterWrite(30, TimeUnit.SECONDS).maximumSize(100).build().asMap(), false);
        }
    };
}

application.yaml:

spring:
  redis:
    host: localhost
  cache:
    type: redis
    cache-names: REDISCACHE
    cache-specs:
      myCustomCache:
        time-to-live: 600s
John Blum
  • 7,381
  • 1
  • 20
  • 30
Pavel Hora
  • 143
  • 1
  • 11

2 Answers2

0

With Spring Boot, you do NOT need to explicitly define/declare a CacheManager bean.

For example, when both the spring-boot-starter-cache (here) and spring-boot-starter-data-redis (here) dependencies are declared on your Spring Boot application classpath, then Spring Boot will auto-configure a CacheManager bean for you. Also, check out this link from start.spring.io, which will get you started.

You still need to enable caching, by declaring the Spring cache @EnableCaching annotation on 1 of your Spring application @Configuration classes (see docs), but you do NOT need to explicitly declare a CacheManager bean. Doing so will actually override the auto-configuration in Spring Boot even, and in your case, that is this Spring Boot auto-configuration class (source) in particular (for Redis). See the Redis CacheManager bean here provided for you by Spring Boot.

WARNING: If you have more than 1 caching provider on your Spring Boot application classpath at a time, then you will need to explicitly declare which provider you intend to use for caching. You can do this with the spring.cache.type property in Spring Boot application.properties, or application.yaml file. See the first "Tip" in the section of the docs.

TIP: You can even customize the Spring Boot auto-configured, Redis-based CacheManager bean by declaring a bean of type RedisCacheManagerBuilderCustomizer (Javadoc, source).

So, what you have effectively done by declaring an explicit CacheManager bean (the ConcurrentMapCacheManager bean, no less), is override Spring Boot's auto-configuration (for instance, this). That is, Spring Boot will only auto-configure a Redis CacheManager bean if you have not explicitly done so yourself.

This is what it means when Spring Boot says, "convention over configuration" unless you explicitly specify yourself, then Spring Boot quietly backs away and assumes you know what you are doing (See more details, here).

To make matters worse, you declared a ConcurrentMapCacheManager bean, which will not use or store anything in Redis, as a caching provider. It will simply use a in-memory, ConcurrentMap (a java.util.concurrent.ConcurrentHashMap to be precise) to cache results from your Spring Boot application service methods, resulting in bypassing Redis altogether.

So, you inadvertently shot yourself in the foot, ;-)

Hope this helps!

John Blum
  • 7,381
  • 1
  • 20
  • 30
  • Hi, but i really want have two caches. One caching data into Redis and second storing data into local memory with time expiration. Every cache is used for another purpose. So what is the simplest way how to do it pls? I found https://stackoverflow.com/questions/56905704/how-to-create-a-secondary-cachemanager-without-overriding-default-spring-cache ...does it get easier? – Pavel Hora Apr 08 '23 at 08:23
  • Hi Pavel- You can certainly use more than 1 caching storage in your Spring [Boot] application services, which requires a bit more effort and coordination, but it is possible. This SO post might give you more insight as well: https://stackoverflow.com/questions/75312195/more-than-1-caching-storage-in-spring-boot-app – John Blum Apr 10 '23 at 17:36
  • Thank you. But i am afraid that it still not solve me problem. Because one of me caches is just a ConcurrentMapCache with expiring map inside ...and she has not any AutoConfigurer. I still dont see any other solution how to create it than explicitly define it like a bean through CacheManager ...with will disable other AutoConfigurer (for Redis in me example) – Pavel Hora Apr 11 '23 at 11:34
  • However, I would say that I might prefer a caching technology that allows you to do both distributed caching in addition to near (local) caching. Both Redis and Hazelcast support this topological caching arrangement. For Redis, see here: https://redis.io/docs/manual/client-side-caching/. For Hazelcast, see here: https://hazelcast.com/blog/non-stop-client-with-near-cache/ – John Blum Apr 11 '23 at 14:27
  • Sorry Pavel. I meant to post my second comment directly above yesterday and forgot to click "Add comment" right of way. `ConcurrentMapCache` is not going to give you great options for configuration (e.g. TTI & TTL) and syncing with the remote cache. Most distributed caching solutions provides a means to receive notifications and updates when the data in a distributed arrangement changes due to multiple clients. – John Blum Apr 11 '23 at 14:29
  • My area of expertise before (Spring Data) Redis was Spring for Apache Geode, an IMDG similar to Hazelcast, and also a caching provider in Spring Framework's Cache Abstraction (the very first one actually). By comparison, you can see the docs (https://docs.spring.io/spring-boot-data-geode-build/current/reference/html5/#geode-caching-provider) on the topic following the link, along with details on different caching patterns here (https://docs.spring.io/spring-boot-data-geode-build/current/reference/html5/#geode-caching-provider-look-aside-near-inline-multi-site) as well as ... – John Blum Apr 11 '23 at 15:41
  • Specifics on Near Caching (https://docs.spring.io/spring-boot-data-geode-build/current/reference/html5/#geode-caching-provider-near-caching). I have also built an example Guide for Near Caching here (https://docs.spring.io/spring-boot-data-geode-build/current/reference/html5/guides/caching-near.html) along with source (https://github.com/spring-projects/spring-boot-data-geode/tree/1.7.5/spring-geode-samples/caching/near) from the examples (https://docs.spring.io/spring-boot-data-geode-build/current/reference/html5/index.html#geode-samples). Hope this helps. – John Blum Apr 11 '23 at 15:43
  • Thank you for your answers John. Like always its comprehensive, helpfull and open new window how to solve, improve architecture. – Pavel Hora Apr 13 '23 at 13:41
  • You are most welcome, Pavel. I wish you success and of course, if you have any more questions, please do not hesitate to ask. Cheers my friend! – John Blum Apr 13 '23 at 21:16
0

From Grampa's Trick Box

@Component
class MyAwesomeCMWrapper {

  final CacheManager redisCache;
  final CacheManager localCache; // or even more complex, e.g.: Map<String, CacheManager> chacheManagerMap;
  
  public MyAwesomeCMWrapper(@Autowired /*@Qualifier(..)*/ CacheManager redisCache) { // <- this is the auto-configured coming from spring 
    this.redisCache = redisCache;
    this.localCache = // init as you like, but *not* as a (direct) bean;)
    // ... new ConcurrentMapCacheManager(...); // <- this is your "local (no-bean) cache)
  }

  // getter...
}

The trick is:

  • not to use a @Bean CacheManager (tricking @ConditionalOnMissingBean(CacheManager.class)/leaving auto configuration)
  • but an "intermediate bean" (which offers acces to "local"/both caches)
  • redisCache is rather optional in this example, key point: "wrap your (autoconfigured + custom) bean"

Then you can access the custom cache manager from spring context:

  • like #{@myAwesomeCMWrapper.localCache} (SpEL)
  • or @Autowried MyAwesomeCMWrapper ... getLocalCache() (java config)
xerx593
  • 12,237
  • 5
  • 33
  • 64