Expand Authors Top

If you have a few years of experience in the Java ecosystem and you’d like to share that with the community, have a look at our Contribution Guidelines.

November Discount Launch 2022 – Top
We’re finally running a Black Friday launch. All Courses are 30% off until next Friday:

>> GET ACCESS NOW

Expanded Audience – Frontegg – Security (partner)
announcement - icon User management is very complex, when implemented properly. No surprise here.

Not having to roll all of that out manually, but instead integrating a mature, fully-fledged solution - yeah, that makes a lot of sense.
That's basically what Frontegg is - User Management for your application. It's focused on making your app scalable, secure and enjoyable for your users.
From signup to authentication, it supports simple scenarios all the way to complex and custom application logic.

Have a look:

>> Elegant User Management, Tailor-made for B2B SaaS

NPI – Lightrun – Spring (partner)

We rely on other people’s code in our own work. Every day. It might be the language you’re writing in, the framework you’re building on, or some esoteric piece of software that does one thing so well you never found the need to implement it yourself.

The problem is, of course, when things fall apart in production - debugging the implementation of a 3rd party library you have no intimate knowledge of is, to say the least, tricky. It’s difficult to understand what talks to what and, specifically, which part of the underlying library is at fault.

Lightrun is a new kind of debugger.

It's one geared specifically towards real-life production environments. Using Lightrun, you can drill down into running applications, including 3rd party dependencies, with real-time logs, snapshots, and metrics. No hotfixes, redeployments, or restarts required.

Learn more in this quick, 5-minute Lightrun tutorial:

>> The Essential List of Spring Boot Annotations and Their Use Cases

1. Overview

In this tutorial, we'll learn how we can configure multiple cache managers in a Spring application.

2. Caching

Spring applies caching to methods so that our application doesn't execute the same method multiple times for the same input.

It's very easy to implement caching in a Spring application. This can be done by adding the @EnableCaching annotation in our configuration class:

@Configuration
@EnableCaching
public class MultipleCacheManagerConfig {}

Then we can start caching the output of a method by adding the @Cacheable annotation on the method:

@Cacheable(cacheNames = "customers")
public Customer getCustomerDetail(Integer customerId) {
    return customerDetailRepository.getCustomerDetail(customerId);
}

As soon as we add the above configuration, Spring Boot itself creates a cache manager for us.

By default, it uses ConcurrentHashMap as the underlying cache if we've not specified any other explicitly.

3. Configuring Multiple Cache Managers

In some cases, we might need to use more than one cache manager in our application. So, let's see how we can do this in our Spring Boot application using an example.

In our example, we'll use a CaffeineCacheManager and a simple ConcurrentMapCacheManager.

CaffeineCacheManager is provided by the spring-boot-starter-cache starter. It'll be auto-configured by Spring if Caffeine is present, which is a caching library written in Java 8.

ConcurrentMapCacheManager uses an implementation of the cache using ConcurrentHashMap.

We can do this in the following ways.

3.1. Using @Primary

We can create two beans of cache managers in our configuration class. Then, we can make one bean primary:

@Configuration
@EnableCaching
public class MultipleCacheManagerConfig {

    @Bean
    @Primary
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager("customers", "orders");
        cacheManager.setCaffeine(Caffeine.newBuilder()
          .initialCapacity(200)
          .maximumSize(500)
          .weakKeys()
          .recordStats());
        return cacheManager;
    }

    @Bean
    public CacheManager alternateCacheManager() {
        return new ConcurrentMapCacheManager("customerOrders", "orderprice");
    }
}

Now, Spring Boot will use CaffeineCacheManager as default for all the methods until we explicitly specify our alternateCacheManager for a method:

@Cacheable(cacheNames = "customers")
public Customer getCustomerDetail(Integer customerId) {
    return customerDetailRepository.getCustomerDetail(customerId);
}

@Cacheable(cacheNames = "customerOrders", cacheManager = "alternateCacheManager")
public List<Order> getCustomerOrders(Integer customerId) {
    return customerDetailRepository.getCustomerOrders(customerId);
}

In the above example, our application will use CaffeineCacheManager for the getCustomerDetail() method. And for the getCustomerOrders() method, it'll use alternateCacheManager. 

3.2. Extending CachingConfigurerSupport

Another way we can do this is by extending the CachingConfigurerSupport class and by overriding the cacheManager() method. This method returns a bean which will be the default cache manager for our application:

@Configuration
@EnableCaching
public class MultipleCacheManagerConfig extends CachingConfigurerSupport {

    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager("customers", "orders");
        cacheManager.setCaffeine(Caffeine.newBuilder()
          .initialCapacity(200)
          .maximumSize(500)
          .weakKeys()
          .recordStats());
        return cacheManager;
    }

    @Bean
    public CacheManager alternateCacheManager() {
        return new ConcurrentMapCacheManager("customerOrders", "orderprice");
    }
}

Note that we can still create another bean called alternateCacheManager. We can use this alternateCacheManager for a method by explicitly specifying it, as we'd in the last example.

3.3. Using CacheResolver

We can implement the CacheResolver interface and create a custom CacheResolver:

public class MultipleCacheResolver implements CacheResolver {
    
    private final CacheManager simpleCacheManager;
    private final CacheManager caffeineCacheManager;    
    private static final String ORDER_CACHE = "orders";    
    private static final String ORDER_PRICE_CACHE = "orderprice";
    
    public MultipleCacheResolver(CacheManager simpleCacheManager,CacheManager caffeineCacheManager) {
        this.simpleCacheManager = simpleCacheManager;
        this.caffeineCacheManager=caffeineCacheManager;
        
    }

    @Override
    public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
        Collection<Cache> caches = new ArrayList<Cache>();
        if ("getOrderDetail".equals(context.getMethod().getName())) {
            caches.add(caffeineCacheManager.getCache(ORDER_CACHE));
        } else {
            caches.add(simpleCacheManager.getCache(ORDER_PRICE_CACHE));
        }
        return caches;
    }
}

In this case, we've got to override the resolveCaches method of the CacheResolver interface.

In our example, we're selecting a cache manager based on the method name. After this, we need to create a bean of our custom CacheResolver:

@Configuration
@EnableCaching
public class MultipleCacheManagerConfig extends CachingConfigurerSupport {

    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager("customers", "orders");
        cacheManager.setCaffeine(Caffeine.newBuilder()
          .initialCapacity(200)
          .maximumSize(500)
          .weakKeys()
          .recordStats());
        return cacheManager;
    }

    @Bean
    public CacheManager alternateCacheManager() {
        return new ConcurrentMapCacheManager("customerOrders", "orderprice");
    }

    @Bean
    public CacheResolver cacheResolver() {
        return new MultipleCacheResolver(alternateCacheManager(), cacheManager());
    }
}

Now we can use our custom CacheResolver to resolve a cache manager for our methods:

@Component
public class OrderDetailBO {

    @Autowired
    private OrderDetailRepository orderDetailRepository;

    @Cacheable(cacheNames = "orders", cacheResolver = "cacheResolver")
    public Order getOrderDetail(Integer orderId) {
        return orderDetailRepository.getOrderDetail(orderId);
    }

    @Cacheable(cacheNames = "orderprice", cacheResolver = "cacheResolver")
    public double getOrderPrice(Integer orderId) {
        return orderDetailRepository.getOrderPrice(orderId);
    }
}

Here, we're passing the name of our CacheResolver bean in the cacheResolver element.

4. Conclusion

In this article, we learned how we can enable caching in our Spring Boot application. Then, we learned three ways by which we can use multiple cache managers in our application.

As always, the code for these examples is available over on GitHub.

November Discount Launch 2022 – Bottom
We’re finally running a Black Friday launch. All Courses are 30% off until next Friday:

>> GET ACCESS NOW

Generic footer banner
2 Comments
Oldest
Newest
Inline Feedbacks
View all comments
Comments are closed on this article!