I usually post about Spring stuff on Twitter - you can follow me there:
Follow @baeldung1. The Cache Abstraction?
In this article we’re going to show how to use the Caching Abstraction in Spring – and generally improve the performance of your system.
We’ll enable simple caching for a some real-world method examples and we’ll discuss how we can practically improve the performance of these calls through smart cache management.
2. Enable Caching
To enable caching, Spring makes good use of annotations, much like enabling any other configuration level feature in the framework.
The caching feature can be declaratively enabled by simply adding the @EnableCaching annotation to any of the configuration classes:
@Configuration @EnableCaching public class MyAppConfig { // Your configuration code goes here. }
You can of course enable cache management with XML configuration as well:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cache="http://www.springframework.org/schema/cache" 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>
Using XML does enable more flexible options to configure caching – you can specify your own Cache-Manager, Cache-Resolver, Error-Handler and generally use more advanced customization options (Refer the javadoc for more details)
3. Use Caching With Annotations
Once you’ve enabled caching, the next step is to bind the caching behavior to the methods with declarative annotations.
3.1. @Cacheable
The simplest way to enable caching behavior for a method is to demarcate it with @Cacheable and parameterize it with the name of the cache where the results would be stored:
@Cacheable("addresses") public String getAddress(Customer customer) {...}
The getCustomerName() call will first checks the cache addresses before actually invoking the method and then caching the result.
While in most cases, one cache is enough, the Spring framework also supports multiple caches to be passed as parameters:
@Cacheable("addresses", “directory”) public String getAddress(Customer customer) {...}
In this case, if any of the cache contains the required result, the result is returned and the method is not invoked.
3.2. @CacheEvict
Now, what would be the problem with making all methods @Cacheable?
The problem is size – we don’t want to populate the cache with values that we don’t need often. Caches can grow quite large, quite fast, and we could be holding on to a lot of stale or unused data.
The @CacheEvict annotation is used to indicate the removal of one or more/all values – so that fresh values can be loaded into the cache again:
@CacheEvict(value="addresses", allEntries=true) public String getAddress(Customer customer) {...}
Here, we’re using the additional parameter allEntries in conjunction with the cache to be emptied – to clear all the entries in the cache addresses and prepare it for new data.
3.3. @CachePut
While @CacheEvict reduces the overhead of looking up entries in a large cache by removing stale and unused entries, ideally, you want avoid evicting to much data out of the cache.
Instead, you’d want to selectively and intelligently update the entries whenever they’re altered.
With the @CachePut annotation, you can update the content of the cache without interfering the method execution. That is, the method would always be executed and the result cached.
@CachePut(value="addresses") public String getAddress(Customer customer) {...}
The difference between @Cacheable and @CachePut is that @Cacheable will skip running the method, whereas @CachePut will actually run the method and then put its results in the cache.
3.4. @Caching
What if you want to use multiple annotations of the same type for caching a method. Look at the incorrect example below:
@CacheEvict("addresses") @CacheEvict(value="directory", key=customer.name) public String getAddress(Customer customer) {...}
The above code would fail to compile, since Java does not allow multiple annotation of the same type to be declared for a given method.
The workaround to the above issue would be:
@Caching(evict = { @CacheEvict("addresses"), @CacheEvict(value="directory", key="customer.name") }) public String getAddress(Customer customer) {...}
As shown in the code snippet above, you can group multiple caching annotations with @Caching, and use it to implement your own customized caching logic.
3.5 @CacheConfig
With the @CacheConfig annotation, you can streamline some of the cache configuration into a single place – at the class level – so that you don’t have to don’t have to declare things multiple times:
@CacheConfig("addresses") public class CustomerDataService { @Cacheable public String getAddress(Customer customer) {...}
4. Conditional Caching
Sometimes, caching might not work well for a method in all situations.
For example – reusing our example from the @CachePut annotation – this will both execute the method as well as cache the results each and every time:
@CachePut(value="addresses") public String getAddress(Customer customer) {...}
4.1 Condition Parameter
Now – if we want more control over when the annotation is active – @CachePut can be parametrized with a condition parameter that takes a SpEL expression to ensure that the results are cached based on evaluating that expression:
@CachePut(value="addresses", condition=”#customer.name=’Tom’”) public String getAddress(Customer customer) {...}
4.2 Unless Parameter
We can also control the caching based on the output of the method rather than the input – via the unless parameter:
@CachePut(value="addresses", unless=”#result.length>64”) public String getAddress(Customer customer) {...}
The above annotation would cache only those customers that have really long addresses (>64 characters).
It’s important to know that the condition and unless parameters can be used in conjunction with all the caching annotations.
This kind of conditional caching can prove quite useful for managing large results and customizing behavior based on input parameters instead of enforcing a generic behavior to all operations.
5. Declarative XML-based Caching
In case you don’t have access to your application’s source code or want to inject the caching behavior externally, you can also use declarative XML- based caching.
The above example can be be translated to:
<!-- the service that you wish to make cacheable --> <bean id="customerDataService" class="com.your.app.namespace.service.CustomerDataService"/> <!-- define caching behavior --> <cache:advice id="cachingBehavior" cache-manager="cacheManager"> <cache:caching cache="addresses"> <cache:cacheable method="getAddress" key="#customer.name"/> </cache:caching> </cache:advice> <!-- apply the behavior to all the implementations of CustomerDataService interface-> <aop:config> <aop:advisor advice-ref="cachingBehavior" pointcut="execution(*com.your.app.namespace.service.CustomerDataService.*(..))"/> </aop:config>
6. Summary
In this article we discussed the basics of Caching in Spring and how to make good use of that abstraction with annotations.