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.