Cache abstraction in Spring 3

In this article we’ll have a look at the new Spring 3 feature: caching by annotation.

I Cache abstraction layer

Caching is an important feature for all applications needing high performance. Many open-source frameworks are available: EhCache, JBoss Cache, OSCache, Open Terracota

Their integration with the application is sometimes easy sometimes more complex, depending on the desired features and use (distributed cache ? cache JMX management …)

Spring 3 introduces a new abstraction layer for caching services. The idea is to provide a set of common features, mainly annotations, to activate and manage the caches.

Since it is only an abstract layer, Spring 3 caching still need a concrete implementation to work. The entry point for cache implementation is the CacheManager interface. By default 2 concrete implementation of CacheManager are provided:

  • EhCacheCacheManager: default implementation for EhCache
  • ConcurrentMapCacheManager: default implementation using Java ConcurrentHashMap as cache store

 

II Configuration

For this example we’ll use EhCache as cache implementation.

First you need to import the Spring cache namespace and add the &th;cache:annotation-driver> tag.

<cache:annotation-driven cache-manager="cacheManager" mode="proxy" order="1" />
  • cache-manager: id or name of the bean implementing the CacheManager interface. Can be omitted because by convention Spring will look for any bean with the name “cacheManager
  • mode: proxy or aspectj. “proxy” will use Spring AOP framework to proxy any class having a caching annotation (see below). “aspectj” will rely on AspectJ aspect for cache management
  • order: optional. If you have more than one aspect declared on the same method, it can be useful to specify the order in which they execute

Then you need to declare the cacheManager bean and implementation instance (or factory bean)

<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
	<property name="cacheManager" ref="ehcache"/>
</bean>

<!-- Ehcache library setup -->
<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
	<property name="configLocation" value="classpath:ehcache.xml"/>
</bean>

First we declare the EhCacheCacheManager as default cache manager. Then we inject the EhCacheManagerFactoryBean so it can retrieve an instance of EhCache class.

Please notice the very confusing naming. The EhCacheCacheManager instance is given the id “cacheManager” as per Spring convention but it also has a cacheManager property. This property is indeed an instance of net.sf.ehcache.CacheManager, different from the org.springframework.cache.CacheManager interface.

Last but not least, you can specify the config file for ehcache with the property configLocation of EhCacheManagerFactoryBean. If not declared it will default to “ehcache.xml

 

III Cache annotations

By default Spring proposes several annotations to manage your caches:

  • @Cacheable: put the method returned value(s) into the cache
  • @CacheEvict: remove an entry from the cache
  • @CachePut: force a cache entry to be updated

And that’s pretty much. But don’t be fooled by their apparent simplicity, they offer a lot of possibilities to fine tune your caching.

A @Cacheable

When annotating a method with @Cacheable, its returned value will be put into the cache provided it meets some condition (if any). Consequently it does not make sense to annotate a void method.

So what can be put in cache ? Pretty much anything, an Object, a collection (List, Map, Set, Array)…

When is the cache activated ? All subsequent calls on the same method with the same arguments or cache key (we’ll see it later) will trigger the cache. Instead of executing the method, the cache is scanned to check whether a matching entry can be found, if yes then it is returned. Otherwise the method is really executed and its result put into the cache.

Below is the pseudo-code:

  • Method is called with arguments args
  • Use the args hashCode or extract the cache key from the args to look for an entry in the cache
  • If a corresponding entry is found
    • Return the cached entry
  • Else
    • Execute really the method
    • Put the method returned value into the cache using the args hashCode of extracted cache key

So what is the cache key and why do we need to use args hashCode ? We’ll see it shortly. First the @Cacheable annotation offers the following attributes:

  • value: mandatory, the name of the cache to work on
  • key: optional, the key used to store and fetch data from the cache
  • condition: optional, specifies the condition to verify before caching an item

Example:

@Cacheable(value = "user-cache", key="#userSearchVO.name", condition="#supportUser == false")
public User findUser(UserSearchVO userSearchVO, boolean supportUser)
{
	User foundUser = null;
	...
	...
	...
	return foundUser;
}

In the above example

  • value = “user-cache” indicates the name of the cache in which entries are stored
  • key=”#searchVO.name” defines the key to lookup into the cache. Here it is the name property of the UserSearchVO object
  • condition=”#supportUser == false” provides the condition to trigger the cache. The cache lookup and the method result caching are triggered only when the supportUser flag is set to false

Please notice that for the key and condition attributes, we are using SpEL to process the method arguments.

This is very convenient because the SpEL expression language is extremely powerfull. For example, if the UserSearchVO has an attribute searchType of enum type, we can do the following:

@Cacheable(value = "user-cache", key="#userSearchVO.name", condition="#userSearchVO.searchType == ${T(com.doan.spring.cache.SearchType).CLASSIC")
public User findUser(UserSearchVO userSearchVO, boolean supportUser)
{
	User foundUser = null;
	...
	...
	...
	return foundUser;
}

public enum SearchType
{
	CLASSIC,
	ADVANCED,
	SUPPORT
}

In this case the cache is triggered only when the search is of type CLASSIC.

Note that we use == instead of equals() to check for the searchType. Since the attribute searchType is an enum, we want to check object identity (same reference) with == rather than object equality (same value) with equals().

Another simple example:

@Cacheable(value = {"user-cache1","user-cache2"})
public User findUserByLoginAndName(String login, String name)
{
	User foundUser = null;
	...
	...
	...
	return foundUser;
}

In this example, we declare more than one cache (“user-cache1”,“user-cache2”) and no key information.

By default if no cache key is provided Spring will compute a hash code of all method arguments (here it is login & name) and use it as a key for cache lookup. If your method arguments are not of primitive type and you want them to be used as cache key, you should redefine properly the hashCode() method.

Last but not least, Spring will scan each cache for key lookup, if an entry is found in any declared cache, it will be returned, subsequent caches are skipped. If all caches are scanned and no entry is found, the method will be executed and the result added to all the declared caches.

 

B @CacheEvict

The @CacheEvict is used to trigger explicit cache eviction. By default most of caching frameworks expire the cache data after some defined duration. The @CacheEvict annotation is usefull when you want to control explicit cache eviction upon some method calls.

 
@CacheEvict(value = "user-cache", key = "#user.login")
public void updateUser(User user)
{
	...
	...
}

The above code is quite self-explanatory. The @CacheEvict annotation exposes the following attributes:

  • value: mandatory, the name of the cache to evict entry
  • key: optional, the key used to lookup data from the cache
  • condition: optional, specifies the condition to verify before evicting a cache entry
  • allEntries: optional, indicates that all entries from the cache should be removed
  • beforeInvocation: optional, indicates whether the cache evict should be done before or after method call

Obviously the key and allEntries attributes are mutually exclusive.

 

C @CachePut

The @CachePut annotation allows you to “update” a cache entry. This is very similar to @Cacheable but for entry update. The @CachePut annotation has exactly the same attributes than @Cacheable.

 
@CachePut(value = "user-cache", key = "#user.login")
public User updateUserName(User user,String newName)
{
	...
	...
	user.setName(newName);
	...
	...
	return user;
}

In the case of @CachePut, the method is always executed and its returned result is put into the cache, using the provided key (or arguments hash), replacing the old entry if necessary.

The only case where the method is not executed is when you provide an optional @CachePut condition and the condition is not met.
 

IV Multiple caching policies

Let’s suppose than we have an application with 2 cache regions: “user-cache” and “user-details-cache”.

@Cacheable(value = "user-cache", key="#login")
public User findUserByLogin(String login)
{
	...
	...
	return foundUser;
}
...
...

@Cacheable(value = "user-details-cache", key="#login")
public UserDetails findUserDetailsByLogin(String login)
{
	...
	...
	return userDetails;
}

It is possible to trigger the eviction from both caches with the same key:

@CacheEvict(value = 
{
	"user-cache",
	"user-details-cache"
}, key="#login")
public void updateUserDetails(String login, UserDetails newUserDetails)
{
	...
	...
}

In this particular example, it’s working very well because we access both caches with the same key. What if we want to evict from those caches with different keys ?

@CacheEvict(value = "user-cache", key="#login")
@CacheEvict(value = "user-details-cache", key="#newUserDetails.id")
public void updateUserDetails(String login, UserDetails newUserDetails)
{
	...
	...
}

It’s simply not possible because Java does not allow you to have more than one type of annotation on the same method.

For this kind of use case, Spring provides a generic @Caching annotation. This annotation simply allows you to group caching annotations of same type like @Cacheable, @CacheEvict or @CachePut.

With this said, our example becomes:

@Caching(evict = {
	@CacheEvict(value = "user-cache", key="#login"),
	@CacheEvict(value = "user-details-cache", key="#newUserDetails.id")
})
public void updateUserDetails(String login, UserDetails newUserDetails)
{
	...
	...
}

And you’re done!

Of course if you try to mix caching annotation of different type in the same @Caching, needless to say that you are running into big trouble…

 

V Object mutability gotcha

So far the caching abstraction infrastructure proposed by Spring is very convenient. However, for having used it in a real project, I’ve spotted 1 pain point : object mutability issues, thought this point is not Spring’s specific but common to all caching frameworks.

Indeed when you get an instance of User after calling findUserByLogin(String login), this instance may come from the cache directly.

If you are modifying this User instance (changing an user property for example), you are indeed modifying the object which is in the cache directly !!!

The immediate consequence is that on the same server, if another client looks for the same user, the cache will give him the same User instance that has been modified earlier…

There are 2 solutions for this issue.

  1. The first idea is to use objects returned from cache as read-only but this rule cannot be enforced easily. Suppose that you put @Cacheable annotation on a Repository or DAO method, the developper that calls this method from Service layer may not be aware that he’s getting a cached instance and may modify it.
  2. The second fix is to perform a deep copy of the returned object. But again, implementing deep copy is not an easy task (as I mentioned in my article about Object Immutablity) and the deep copy should be done by the caller (Service layer in our example). The same issue with “knowing that we’re dealing with cached instances” mentioned above also applies.

Fortunately, latest EHCache versions provide a very usefull feature: copyOnRead.

<cache name="user-cache"
	...
	eternal="false"
	timeToIdleSeconds="10"
	timeToLiveSeconds="1800"
	overflowToDisk="false"
	copyOnRead="true"
>
</cache>

With this flag turned on, EHCache will return you a deep copy of the object instance that is in the cache, not the instance itself. So mutability issue is solved.

Of course there is no free lunch, this convenient feature comes with its counterparts, the cost of serialization/deserialization because the default implementation is plain Java object serialization.

 

10 Comments

  1. Mario Guerrero

    Hi

    Thanks for the doc.

    Can i use distributed cache with spring ?

    Reply
    1. DuyHai DOAN

      You can use distributed EHCache with Terracotta:

      http://ehcache.org/documentation/configuration/distributed-cache-configuration

      Reply
      1. Mario Guerrero

        Thanks DuyHai.

        I’ll try with terracotta.

        Thanks.

        Reply
    2. Ragnor

      You may also try memcached using Simple Spring Memcached: http://code.google.com/p/simple-spring-memcached/ . In version 3.0.0 it provides integration with Spring Cache.

      Reply
  2. Zebra

    Have you tried this to evict from cache with different keys:

    @Caching(evict = {@CacheEvict(value = “user-cache”, key=”#login”) ,
    @CacheEvict(value = “user-details-cache”, key=”#newUserDetails.id”) })

    Reply
    1. DuyHai DOAN

      Good point Zebra ! I miss the @Caching annotation. It was not documented in the 3.1.0 M2 release : http://static.springsource.org/spring/docs/3.1.0.M2/javadoc-api/ but is present in the official release doc.

      I will update the post to add it. Thanks!

      Reply
  3. Pingback: Today in bookmarks for August 29th. | ngerakines.me

  4. Deepesh

    I am using @Cacheable and able to catch my map with objects at startup but when I tried to add elements with @CachePut, it does not work. Please help me what may the problem or missing here.. I am passing the same object with this annotation which initially is cached.

    Thanks !

    Reply
  5. Pingback: Simple Spring Memcached – Spring Caching Abstraction and Memcached | Java Logs

  6. Pingback: spring 3 caching abstraction | Talipkorkmaz's Blog

Leave a Reply to Deepesh Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.