In this post, we will take a deeper look at Spring caching. Spring caching helps to reduce the load and improve the performance of the system.
Spring Caching
Spring 3.1 introduced support for transparently adding caching into an existing Spring application. Similar to the transaction support, the caching abstraction allows consistent use of various caching solutions with minimal impact on the code. Spring 4.1, the cache abstraction has been significantly improved with the support of JSR-107 annotations and more customization options.
Spring provides supports for multiple cache providers, with Spring Boot auto configuration, configuring and enabling a specific cache providers is more easy. Spring Boot will automatically configure a specific cache provider in case it finds the jar in the class path (e.g It will automatically configure Ehcache if it find the jar in the class-path). By default Spring Boot will configure a default cache provider using the ConcurrenthashMap
.
1. Enable Spring Caching
To enable Spring caching support, we need to take care of following two important points.
- We need to identify and annotate methods that need to be cached.
- Cache configuration: Enable caching support
In order to enable caching support in Spring, we need to enable this feature either using annotation or the traditional xml based configuration.
1.1 Enable Caching Annotation Support
To enable caching annotation, we need to add the annotation @EnableCaching to one the application @Configuration class.
@Configuration
@EnableCaching
public class AppConfig {
// Cache Manager configurations
}
The @EnableCaching
annotation triggers a post processor that inspects every Spring bean for the presence of caching annotations on public methods. If such an annotation is found, a proxy is automatically created to intercept the method call and handle the caching behavior accordingly.
1.2 Enable Caching By XML Configuration
To enable XML based configuration, use thecache:annotation-driven
element.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
<cache:annotation-driven />
</beans>
1.3 Configuring Cache Manager
Spring caching abstraction provides integration with several cache providers. Define proper CacheManager
to control and manageCache
s in your application.
<!-- simple cache manager -->
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="default"/>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="product"/>
</set>
</property>
</bean>
Java Configuration
@Configuration
@EnableCaching
public class AppConfig {
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("product");
}
}
To use third-party cache provider, please refer to Spring documentation for defining cache provider for your application.
1.4 Spring Boot Cache Configuration
Spring Boot provides good support for caching abstraction. We will be using Spring Boot for this tutorials. Spring Boot use a simple implementation using a ConcurrentHashMap
as the cache store. This is the default if no caching library is present in your application.
We will cover Spring Boot cache integration in a separate post.
2. Annotation Based Caching
The caching API provides a set of Java annotations available for use.
Annotation | Description |
@Cacheable | Triggers cache population |
@Caching | Regroups multiple cache operations |
@CacheConfig | Shares some common cache-related settings at class-level |
@CacheEvict | Cache eviction |
@CachePut | Updates the cache |
2.1 @Cacheable
The @Cacheable
annotation used to demarcate methods that are cacheable. In simple words, this annotation used to indicate caching API that we want to store results for this method into the cache so, on subsequent invocations, the value in the cache returned without having to actually execute the method.
@Cacheable("products")
public List<Product> getProducts() {...}
The getProducts() will first check the cache “products” before actually invoking the method and then caching the result. This annotation allows multiple names to be specified so that more than one cache is being used.
@Cacheable("baseProducts","varaintProducts")
public List<Product> getProducts() {...}
In this case, if any of the caches contain the required result, the result returned and the method is not invoked.
2.2 @CacheEvict
This annotation work quite opposite to the @Cacheable
annotation. The @CacheEvict
annotation used for removing stale or unused data from the cache. Removing data from the cache is important else cache can grow quite large and may be holding unused data.
@CacheEvict(cacheNames="products",allEntries=true)
public List<Product> getProducts() {...}
Let’s take a look at the above annotation
- This annotation allows us to specify cacheName which require eviction.
- We can specify one or multiple caches for clearance.
- Specify extra parameter which
allEntries
indicates whether a cache-wide eviction required and not just an entry one.
We can also control the eviction process using beforeInvocation
attribute. Use this attribute to control whether the eviction should occur after (the default) or before the method execution.
2.3 @CachePut
The @CachePut annotation is useful if we want to update cache without affecting method execution. The @CacheEvict
annotation can create an issue as we are trying to evict a lot of data from the cache, @CachePut
is an intelligent option to selectively updated entries.
@CachePut(value="products")
public List<Product> getProducts() {...}
[pullquote align=”normal”]Do not use @CachePut and @Cacheable annotations on the same method. [/pullquote]
- @Cacheable – causes the method execution skipping by using the cache.
- @CachePut – the method will always be executed and its result placed into the cache.
Using both annotations on the same method leads to unexpected results.
2.4 @CacheConfig
The @CacheConfig
is a class level annotation and help to streamline caching configurations. This annotation helps to centralize some of the configurations so we do not need to specify these configurations at each step.
@CacheConfig("products")
public class ProductService{
@Cacheable
public List<Product> getProducts() {...}
}
In above example, we specify the name of the cache to use for every cache operation at the class level (single definition), We do not need to specify cache name at the method level.
2.5 @Caching
The @Caching
annotation allows multiple nested caching annotations on the same method. This helps us to handle use cases where we want to use multiple annotations of the same type. Let’s take an example to understand it.
@CacheEvict("baseProducts")
@CacheEvict(value="variants", key="#key")
public List<Product> getProducts()() {...}
Java does not allow multiple annotations of the same type hence the above code will fail to compile. The @Caching
annotation provides a solution to handle these use cases.
@Caching(evict = { @CacheEvict("baseProducts"), @CacheEvict(cacheNames="variants", key="#key") })
public List<Product> getProducts()() {...}
3. Caching Key Generation
Cache is always a key-value storage and Spring caching is not different in this. This API provides a number of options for us to customize and control key generation process.
3.1 Default Key Generation
Spring API use a simple KeyGenerator
based on the following steps
- If we do not specify any parameter, it returns SimpleKey.EMPTY.
- Return the same instance if only one parameter is given.
- Create and return SimpleKey if more than one parameter passed.
Read for more detail on key generation.
Default key generation is capable to fulfill most of the use cases provided out code base meets following requirements
- The parameter should have natural keys (like code, unique key etc.)
- Have a valid implementation of
hashCode()
andequals()
methods.
3.2 Custom Key Generation
If the default key generation is not enough for your need, you can always opt for the custom key generation mechanism for the Spring cache. We can SpEL
to pick the arguments of interest (or their nested properties), perform operations or even invoke arbitrary methods without having to write any code or implement any interface.
@Cacheable(cacheNames="address", key="#customer")
public Address getAddress(final Customer customer)() {...}
@Cacheable(cacheNames="address", key="#customer.id")
public Address getAddress(final Customer customer)() {...}
API also provides an option to define a custom keyGenerator
on the operation.
@Cacheable(value="products",keyGenerator="customKeyGenerator")
public List<Product> getProducts() {...}
Read Spring Cache Custom KeyGenerator for more details.
4. Cache Synchronization
In a multithread environment, certain operation might be concurrently invoked for the same argument, this means that same value may be added/updated several times during these operations. We may want to avoid this situation since it will defeat the purpose of the caching.
Spring 4.2+ introduced support to handle these use cases with the help of the sync
attribute. Use of the sync
attribute with @Cacheable annotation instructs the underlying cache provider to lock the cache entry while the value is being computed. To put it in simple words
- Only one thread will compute the value.
- All other threads will be in block status until the value computed and put in the cache by the first thread.
@Cacheable(value="products",sync=true)
public List<Product> getProducts() {...}
5. Conditional Caching
Sometimes, a method might not be suitable for caching all the time and we may want to cache to come in to picture for certain conditions. The cache annotations support such functionality through the condition
parameter which takes a SpEL
expression.
To understand it, let’s take an example where we only want to cache the US-based address.
5.1 Condition Parameter
@Cacheable(cacheNames="address", condition="#customer.profile.country='US'")
public Address getAddress(final Customer customer)() {...}
5.2 Unless Parameter
Unlike,condition
unless
expressions are evaluated after the method has been called.
@Cacheable(cacheNames="address", unless="#customer.profile.country='US'")
public Address getAddress(final Customer customer)() {...}
6. Custom Caching Annotation
Spring caching abstraction support creating a custom annotation to eliminate the need to duplicate cache annotation declarations. To use this approach, we need to create a custom annotation and annotate it with custom caching annotation.
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Cacheable(cacheNames = "products")
public @interface CustomCachingAnnotation {
}
To use this, we are simply replacing @Cacheable annotation with @CustomCachingAnnotation
@CustomCachingAnnotation
public List<Product> getProducts() {...}
7. XML-based Caching
If the annotation is not an option for you, we can use XML for declarative caching.
!-- the service we want to make cacheable -->
<bean id="productService" class="com.javadevjournal.service.product.DefaultProductService"/>
<!-- cache definitions -->
<cache:advice id="cacheAdvice" cache-manager="cacheManager">
<cache:caching cache="products">
<cache:cacheable method="getProducts" all-entries="true"/>
</cache:caching>
</cache:advice>
<!-- apply the cacheable behavior to all BookService interfaces -->
<aop:config>
<aop:advisor advice-ref="cacheAdvice" pointcut="execution(* com.javadevjournal.service.product.efaultProductService.*(..))"/>
</aop:config>
Summary
In this post, we discussed the basic of using Spring caching in your application. We covered basic building blocks to get an understanding of making good use of caching in your application. Check the GitHub project for the complete source code.
Hi, could you give a tutorial or the CacheManager java config for cache a list to redis? Thanks
Hi Soleil,
If you have the Redis available in the classpath, Spring Boot will auto configure a RedisCacheManager for you.One of the best thing about Spring caching is the Abstraction and it is same for everything (You can use same logic to store data in cache regardless of underlying caching API).
You can take a look at additional caching properties at https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-caching.html#boot-features-caching-provider-redis