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.

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 the cache: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 manage Cache 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"); } }

[pullquote align=”normal”]To use third-party cache provider, please refer to Spring documentation for defining cache provider for your application. [/pullquote]

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() and equals() 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.