Search This Blog

Thursday, 27 September 2012

Caching in Spring-2

In our previous post we saw how to use Spring and  Ehcache. We did that through a lot of XML configuration. Spring also provides us with annotations to use.
I decided to try using Spring caching implementations based on a  ConcurrentMap. The annotated service class is:
public class StudentService {

    private static Map<Integer, String> students = new LinkedHashMap<Integer, String>();
    
    static {
        students.put(1, "Robin");
        students.put(2, "Reena");
        students.put(3, "Ramesh");
        students.put(4, "Virat");
        students.put(5, "Neeta");
    }
    
    @Cacheable(value = "studentNameByIdCache")
    public String getStudentName(final int id) {
        System.out.println("Request received at target bean " + id);
        return students.get(id);
    }
    
    @CacheEvict(value = "studentNameByIdCache")
    public void remove(final int id) {
        students.remove(id);
    }
    
    @CacheEvict(value = "studentNameByIdCache", allEntries=true)
    public void clearAndPut(final int id, final String name) {
        students.clear();
        students.put(id, name);
    }
}
The @Cacheable annotation is used above the method that we need to cache. The value attribute specifies the name of the cache. The other attributes are key and condition -
  • key attribute specifies the key used for the cache. Default is "", meaning all method parameters are considered as a key.
  • The condition attribute is a Spring Expression Language  attribute used to specify the condition that decides if a particular request must be cached.
The other two methods in the class help decide when the cache needs to be flushed. In case of remove() method, we only need to remove a single entry from the cache. In this case the key attribute's default value is used which is the method parameters - i.e the id. For the clearAndPut() method we have set the allEnteries attribute to true. This will clear the cache of all entries.
The xml configuration is now minimal.
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:cache="http://www.springframework.org/schema/cache"        
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-3.1.xsd    
    http://www.springframework.org/schema/cache  
    http://www.springframework.org/schema/cache/spring-cache-3.1.xsd">
    
    <cache:annotation-driven cache-manager="cacheManager" />
    
    <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
        <property name="caches">
            <set>
                <bean
                    class="org.springframework.cache.concurrent.ConcurrentMapCache">
                    <constructor-arg value="default"/>    
                </bean>
                <bean
                    class="org.springframework.cache.concurrent.ConcurrentMapCache">
                    <constructor-arg value="studentNameByIdCache"/>    
                </bean>
            </set>
        </property>
    </bean>

    <bean id="serviceBean" class="com.service.annotated.StudentService" />
        
</beans>
As can be seen we
  1. included the annotation-driven element to tell Spring that caching is via annotations. We also specified the name of our cache manager bean
  2. defined an instance of the cache manager. We are using a SimpleCacheManager. The class works against a given collection of caches. We have defined the cache as inner beans for the set.
  3. We created our service bean class as always.
The code to test the above implementation is :
public static void main(String[] args) {
    final ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
            "spring-map-cache.xml");
    final StudentService service = (StudentService) applicationContext
            .getBean("serviceBean");
    System.out.println("get 1 " + service.getStudentName(1));
    System.out.println("get 2 " + service.getStudentName(2));
    System.out.println("get 1 " + service.getStudentName(1));// from cache
    System.out.println("Removing 2");
    service.remove(2);
    System.out.println("get 1 " + service.getStudentName(1));// from cache
    System.out.println("get 2 " + service.getStudentName(2));
    System.out.println("Clear all and add 1");
    service.clearAndPut(1, "Reema");
    System.out.println("get 1 " + service.getStudentName(1));
    System.out.println("get 1 " + service.getStudentName(1));// from cache
}
The output is as below:
Request received at target bean 1
get 1 Robin
Request received at target bean 2
get 2 Reena
get 1 Robin
Removing 2
get 1 Robin
Request received at target bean 2
get 2 null
Clear all and add 1
Request received at target bean 1
get 1 Reema
get 1 Reema

2 comments:

  1. Hi,

    Nice post. Can you please explain the process how we can use database level cache with that. How we should keep update each record and provide cache as well using this. Please explain this

    Thanks

    ReplyDelete
  2. Hi Noman,
    Unfortunately I haven't really worked with caching at the db level. But will surely look into it once I get some time. Thanks for thepointer.

    ReplyDelete