Partner – Microsoft – NPI (cat= Spring)
announcement - icon

Azure Spring Apps is a fully managed service from Microsoft (built in collaboration with VMware), focused on building and deploying Spring Boot applications on Azure Cloud without worrying about Kubernetes.

And, the Enterprise plan comes with some interesting features, such as commercial Spring runtime support, a 99.95% SLA and some deep discounts (up to 47%) when you are ready for production.

>> Learn more and deploy your first Spring Boot app to Azure.

You can also ask questions and leave feedback on the Azure Spring Apps GitHub page.

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.

Course – LS (cat=Spring)

Get started with Spring and Spring Boot, through the Learn Spring course:

>> THE COURSE
res – REST with Spring (eBook) (everywhere)
2 Comments
Oldest
Newest
Inline Feedbacks
View all comments
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.