Caching in Spring

SHARE & COMMENT :

Introduction

In this article, we will discuss about the Caching support provided by the Spring framework. This article assumes that the reader has a basic understanding on Spring framework. The first section of the article illustrates the various APIs provided by Spring such as Cache, Cache Manager and Composite Cache Manager. Plenty of code samples will be provided for illustrating the concepts better. Caching support can be specified for methods using annotations also and the later section of this article provides an in-depth discussion on its usage. If you are not familiar with the spring framework, please read our previous articles introduction to spring, spring aop and spring mvc.

also read:

Download Spring Catching Sample Code

Caching

Caching in Spring is abstracted and is represented through the ‘Cache’ interface. A cache is given and identified through a name and this interface provides methods for placing objects into a cache, retrieving objects from the cache, checking if an object is present in the cache for a given key, removing objects from a cache, replacing existing objects from a cache etc.

package net.javabeat.articles.spring.cache.basic;

import java.util.concurrent.ConcurrentMap;

import org.springframework.cache.Cache;
import org.springframework.cache.concurrent.ConcurrentCache;

public class SimpleCacheTest {

	public static void main(String[] args) {

		String cacheName = 'colorsCache';
		Cache colorsCache = new ConcurrentCache(cacheName);

		testPut(colorsCache);
		testCacheName(colorsCache);
		testPutIfAbsent(colorsCache);
		testRemoveAndReplace(colorsCache);
		testNative(colorsCache);
	}

	private static void testPut(Cache colorsCache){

		colorsCache.put('RED', 'Red');
		colorsCache.put('BLUE', 'Blue');
		colorsCache.put('GREEN', 'Green');

		System.out.println('Cache contains the key 'BLUE'' + colorsCache.containsKey('BLUE'));
		System.out.println('Cache contains the key 'YELLOE'' + colorsCache.containsKey('YELLOW'));

		System.out.println('Value for the key 'RED' is ' + colorsCache.get('RED'));
		System.out.println('Value for the key 'BLACK' is ' + colorsCache.get('BLACK'));

	}

	private static void testCacheName(Cache colorsCache){
		System.out.println('Name of the cache is ' + colorsCache.getName());

	}

	private static void testPutIfAbsent(Cache colorsCache){

		colorsCache.putIfAbsent('RED', 'NEW_RED');
		System.out.println('Value for the key 'RED' is ' + colorsCache.get('RED'));

		colorsCache.put('RED', 'NEW_RED');
		System.out.println('Value for the key 'RED' is ' + colorsCache.get('RED'));

	}

	private static void testRemoveAndReplace(Cache colorsCache){

		colorsCache.remove('BLUE');
		colorsCache.replace('GREEN', 'LIGHT_GREEN');

		System.out.println('Value for the key 'GREEN' is ' + colorsCache.get('GREEN'));
	}

	private static void testNative(Cache colorsCache){

		Object nativeCache = colorsCache.getNativeCache();
		if (nativeCache instanceof ConcurrentMap){

			@SuppressWarnings('unchecked')
			ConcurrentMap map = (ConcurrentMap)nativeCache;
			System.out.println('All Keys -->' + map.keySet());
			System.out.println('All values -->' + map.values());
		}
	}
}

Currently two cache implementations are shipped with spring release – one is the simple cache implementation that makes use of ConcurrentMap and the other is Ehcache implementation. The above example illustrates the various operations that can be performed on a cache. A new cache is created with the name ‘colorsCache’. The method ‘testPut’ illustrates the operation of adding elements to the cache using the ‘put’ method. Note a cache entry is also accompanied with a key and a name. The method ‘containsKey’ can be used to check if a particular key exists on a cache. The method ‘get’ retrieves an element from the cache for a given key.
There is one variation of put() method which populates the cache conditionally, the method name is putIfAbsent(). This method will check if the key passed to this method is already present in the cache. If the key is not present, then the cache element will be added. For removing an element from the cache, the method remove() can be used which we passed with a key will attempt to remove the element. The method replace() can be used to replace an element for an existing key.

Cache Manager

It is possible to manage multiple caches and this is achieved in Spring with the help of CacheManager interface. Through this interface it is possible to dynamically add and remove caches at runtime.

package net.javabeat.articles.spring.cache.basic;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.concurrent.ConcurrentCache;
import org.springframework.cache.support.SimpleCacheManager;

public class SimpleCacheManagerTest {

	public static void main(String[] args) {

		SimpleCacheManager cacheManager = new SimpleCacheManager();

		List<Cache<!--?,?-->> cacheList = new ArrayList<Cache<!--?,? -->>();

		Cache colorsCache = new ConcurrentCache('colorsCache');
		colorsCache.put('RED', 'Red');
		colorsCache.put('BLUE', 'Blue');
		colorsCache.put('GREEN', 'Green');
		cacheList.add(colorsCache);

		Cache countriesCache = new ConcurrentCache('countriesCache');
		countriesCache.put('IND', 'India');
		countriesCache.put('JAPAN', 'Japan');
		countriesCache.put('USA', 'America');
		cacheList.add(countriesCache);

		Cache> mathCache = new ConcurrentCache>('mathCache');
		mathCache.put('evenNumbers', new HashSet(Arrays.asList(0, 2, 4, 6, 8)));
		mathCache.put('oddNumbers', new HashSet(Arrays.asList(1, 3, 5, 7, 9)));
		mathCache.put('primeNumbers', new HashSet(Arrays.asList(1, 3, 5, 7, 11)));
		cacheList.add(mathCache);

		Collection<Cache<!--?,?-->> cacheCollection = cacheList;
		cacheManager.setCaches(cacheCollection);
		cacheManager.afterPropertiesSet();

		testCacheNames(cacheManager);
		testCache(cacheManager);
	}

	private static void testCacheNames(CacheManager cacheManager){

		for (String cacheName : cacheManager.getCacheNames()){
			System.out.println('Cache name is ' + cacheName);
		}
	}

	private static void testCache(CacheManager cacheManager){

		System.out.println('Value of 'IND' from 'countriesCache' is '
			+ cacheManager.getCache('countriesCache').get('IND'));

		System.out.println('Value of 'oddNumbers' from 'mathCache' is '
			+ cacheManager.getCache('mathCache').get('oddNumbers'));
	}
}

In the above code, the program creates three different caches with the name ‘colorsCache’, ‘countriesCache’ and ‘mathCache’ and the cache is populated with some test elements. One implementation of the CacheManager interface is the ‘SimpleCacheManager’ class and an instance of the same is created and the caches are set by calling the method ‘setCaches’. Calling the method ‘getCacheNames()’ will retrieve the list of cache names managed by this cache manager. For retrieving an element from the cache, a reference to the cache must be obtained first by calling the method getCache() and then the method get() can be called to obtain the desired element from the cache.

Composite Cache Manager

Simply put, a composite cache manager can be used to manage multiple cache managers containing multiple caches. A composite manager is represented through the interface ‘CompositeCacheManager’ and multiple cache manager can be managed by calling the method ‘setCacheManagers’ defined on the cache manager object as the following code illustrates this.

package net.javabeat.articles.spring.cache.basic;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.concurrent.ConcurrentCache;
import org.springframework.cache.support.CompositeCacheManager;
import org.springframework.cache.support.MapCacheManager;
import org.springframework.cache.support.SimpleCacheManager;

public class CompositeCacheManagerTest {

	public static void main(String[] args) {

		CompositeCacheManager compositeCacheManager = new CompositeCacheManager();

		SimpleCacheManager simpleCacheManager = createSimpleCacheManager();
		MapCacheManager mapCacheManager = createMapCacheManager();

		CacheManager[] cacheManagers = {simpleCacheManager, mapCacheManager};
		compositeCacheManager.setCacheManagers(cacheManagers);

		testCache(compositeCacheManager);
	}

	private static SimpleCacheManager createSimpleCacheManager(){

		SimpleCacheManager cacheManager = new SimpleCacheManager();

		Cache databasesCache = new ConcurrentCache('databasesCache');
		databasesCache.put('ORACLE', 'Oracle');
		databasesCache.put('DB2', 'Db2');
		databasesCache.put('SQL_SERVER', 'Sql Server');

		List<Cache<!--?,?-->> cacheList = new ArrayList<Cache<!--?,? -->>();
		cacheList.add(databasesCache);

		cacheManager.setCaches((Collection<cache>)cacheList);

		cacheManager.afterPropertiesSet();
		return cacheManager;
	}

	private static MapCacheManager createMapCacheManager(){

		MapCacheManager mapCacheManager = new MapCacheManager();

		Cache departmentsCache = new ConcurrentCache('departmentsCache');
		departmentsCache.put('IT', 'IT');
		departmentsCache.put('ADMIN', 'Admin');
		departmentsCache.put('FINANCE', 'Finance');

		List<Cache<!--?,?-->> cacheList = new ArrayList<Cache<!--?,? -->>();
		cacheList.add(departmentsCache);

		mapCacheManager.setCaches((Collection<cache>)cacheList);
		mapCacheManager.afterPropertiesSet();

		return mapCacheManager;
	}

	private static void testCache(CompositeCacheManager cacheManager){

		System.out.println('Value of 'ADMIN' from 'departmentsCache' is ' +
			cacheManager.getCache('departmentsCache').get('ADMIN'));
		System.out.println('Value of 'DB2' from 'databasesCache' is ' +
				cacheManager.getCache('databasesCache').get('DB2'));
	}
}

In the above code, an instance of composite cache manager is created. Then the code attempts to create multiple cache manager instances, the first one being an implementation of Simple Cache Manager and the other being an implementation of Map Cache manager. When using a composite manager, for retrieving an element, a reference to the cache must be obtained first and subsequently the element has to be retrieved.

Caching Annotations

In this section, we will see how to make use of the spring Caching annotations for supporting Caching functionality. More specifically we will see the usage of the annotation ‘@Cacheable’. We will consider a Customer Data access object in this example which provides API methods for fetching customer objects based on various parameters. First, have a look at the customer model which has properties such as ‘id’, ‘name’ and ‘type’. The complete code listing is given below.

package net.javabeat.articles.spring.cache.annotation;

public class Customer {

	private String id;
	private String name;
	private String type;

	public Customer(String id, String name, String type){
		this.id = id;
		this.name = name;
		this.type = type;
	}

	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}

	public String getType() {
		return type;
	}

	public void setType(String type) {
		this.type = type;
	}

	@Override
	public int hashCode() {
		return id.hashCode();
	}

	@Override
	public boolean equals(Object obj) {

		if (obj instanceof Customer){

			Customer customerObj = (Customer)obj;
			return id.equals(customerObj.id);
		}
		return false;
	}
}

Have a look at the Customer Dao interface. This interface provides method for retrieving a customer object for a given customer id. It also provides a method for retrieving all the customer objects. Note the usage of the @Cacheable annotation defined on the methods. This declaration of the annotation on a method provides an indication to the framework that the return results of the method has to be cached. This means that when the method is called for the first time, the method execution will happen normally and the result will be cached. The second time when the method is called, the method won’t be executed and the stored result from the cache will be retrieved and returned.

package net.javabeat.articles.spring.cache.annotation;

import java.util.Set;

import org.springframework.cache.annotation.Cacheable;

public interface CustomerDao {

	@Cacheable(value = {'customer'})
	Customer findCustomer(String customerId);

	@Cacheable('allCustomers')
	Set getAllCustomers();

	@Cacheable(value = {'customers'}, condition = '#type == 'regular'')
	Set getCustomers(String type);
}

Through ‘@Cacheable’ annotation, a name is specified to the value attribute. This value must match with the value that will be specified in the configuration file which we will see later. One useful attribute of the ‘@Cacheable’ annotation is the condition attribute. This attribute takes an expression and the method return results will be cached only when the expression specified as part of the condition evaluates to true. The implementation of the customer dao is given below.

package net.javabeat.articles.spring.cache.annotation;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class CustomerDaoImpl implements CustomerDao{

	private static Map MAP;

	public Customer findCustomer(String customerId){

		waitFor();
		return MAP.get(customerId);
	}

	@Override
	public Set getAllCustomers() {

		waitFor();
		return new HashSet(MAP.values());
	}

	public Set getCustomers(String type){

		System.out.println('For type ->' + type);

		Set customers = new HashSet();
		Iterator<Map.Entry> iterator = MAP.entrySet().iterator();
		while (iterator.hasNext()){

			Map.Entry entry = iterator.next();
			Customer customer = entry.getValue();
			if (type.equals(customer.getType())){
				customers.add(customer);
			}
		}
		return customers;
	}

	private void waitFor(){
		try{
			Thread.sleep(3 * 1000);
		}catch (Exception e){
			e.printStackTrace();
		}
	}

	static{
		MAP = new HashMap();

		MAP.put('1', new Customer('Mike', 'Michael', 'Regular'));
		MAP.put('2', new Customer('Jame', 'James', 'Premium'));
		MAP.put('3', new Customer('Chris', 'Christopher', 'Regular'));
		MAP.put('3', new Customer('Jeff', 'Jeff', 'Premium'));
	}
}

Note that the implementation internally uses a map for storing and retrieving customer objects. Note that each implementation method pauses for a while and the execution starts after. This is just made to ensure that the first time the method is invoked it will take a considerable time and on the second time the execution will be faster because the values are retrieved from the cache.

package net.javabeat.articles.spring.cache.annotation;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

	public static void main(String[] args) {

		ClassPathXmlApplicationContext applicationContext =
			new ClassPathXmlApplicationContext('annotation-config.xml');

		CustomerDao customerDao = applicationContext.getBean(CustomerDao.class);

		testGetAllCustomers(customerDao);
		testGetCustomer(customerDao, '2');
		testAllCustomers(customerDao);
	}

	private static void testGetAllCustomers(CustomerDao customerDao){

		System.out.println();

		long startTime = System.currentTimeMillis();
		customerDao.getAllCustomers();
		long endTime = System.currentTimeMillis();
		System.out.println('First invocation on getAllCustomers took ' + (endTime - startTime));

		startTime = System.currentTimeMillis();
		customerDao.getAllCustomers();
		endTime = System.currentTimeMillis();
		System.out.println('Second invocation on getAllCustomers took ' + (endTime - startTime));
	}

	private static void testGetCustomer(CustomerDao customerDao, String customerId){

		System.out.println();

		long startTime = System.currentTimeMillis();
		customerDao.findCustomer(customerId);
		long endTime = System.currentTimeMillis();
		System.out.println('First invocation on findCustomer took ' + (endTime - startTime));

		startTime = System.currentTimeMillis();
		customerDao.findCustomer(customerId);
		endTime = System.currentTimeMillis();
		System.out.println('Second invocation on findCustomer took ' + (endTime - startTime));
	}

	private static void testAllCustomers(CustomerDao customerDao){

		customerDao.getCustomers('regular');
		customerDao.getCustomers('premium');

		customerDao.getCustomers('regular');
		customerDao.getCustomers('premium');
	}
}

In the main class, various test methods are added for testing the Caching behavior. In the method ‘testGetAllCustomers()’, the method ‘getAllCustomers()’ defined on the ‘Customer Dao’ instance is called two times. When this method is called for the first time, the method implementation will be invoked when the caller will be paused for some seconds, then the results will be returned from the method. For the second call, because the method is already invoked, the results for this method which were already available from the cache and will taken and returned. Considering the test method ‘testAllCustomers’ the method getCustomers() is called multiple time by passing in the type of the customer. In the getCustomers() method that expects type parameter, we have specified that only when the customer type is ‘regular’ the Caching behavior must be incorporated. This means that for any value other than ‘regular’, the Spring Caching behavior will not be incorporated and the method will behave normally.

<?xml version='1.0' encoding='UTF-8'?>
<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 cache-manager='simpleCacheManager'/>

<bean id='simpleCacheManager'>
<property name='caches'>
<set>
<bean>
<property name='name' value='customer'/>
</bean>
<bean>
<property name='name' value='allCustomers'/>
</bean>
<bean>
<property name='name' value='customers'/>
</bean>
</set>
</property>
</bean>

<bean id='customerDao' >
</bean>

</beans>

The Spring configuration file for supporting and enabling Caching is given above. Note that the element ‘cache:annotation-driven’ has to be specified for enabling Spring annotation based Caching. By default, the attribute value ‘cache-manager’ has the default value as ‘cacheManager’ for looking up the cache manager instance.

also read:

Conclusion

In this article, we had discussed about the Caching support provided by Spring framework. It explained various aspects of Caching by elaborating details related to cache, cache manager and composite cache manager. The later section of the article describes the Spring Caching annotations.
If you have any questions on the spring catching, please post it in the comments section. Also search in our website to find lot of other interesting articles related to the spring framework. There are some interesting articles about spring framework, interview questions, spring and hibernate integration,etc.
If you would like to receive the future java articles from our website, please subscribe here.

Comments

comments

About Krishna Srinivasan

He is Founder and Chief Editor of JavaBeat. He has more than 8+ years of experience on developing Web applications. He writes about Spring, DOJO, JSF, Hibernate and many other emerging technologies in this blog.

Trackbacks

  1. […] in Spring Roo Framework Spring Roo and JPA Entities Configure Spring Batch to Retrying on Error Caching in Spring Spring DMs Event Admin Service Support What’s new in Spring 3.0? Annotation Based Bean Wiring […]

Speak Your Mind

*

Close
Please support the site
By clicking any of these buttons you help our site to get better