Tuesday, April 1, 2014

How I set up ehCache

Ehcache is a nice way to cache method responses. This is nice for example if one of your methods calls a web service or takes very long to execute.

I used ehcache with Spring, so I had to do some extra stuff so the annotations would work.

First I added ehcache and google's ehcache annotations for spring to my pom


<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache-core</artifactId>
    <version>2.6.0</version>
</dependency>
<dependency>
    <groupId>com.googlecode.ehcache-spring-annotations</groupId>
    <artifactId>ehcache-spring-annotations</artifactId>
    <version>1.2.0</version>
</dependency>

Then in my Spring context file I added:
<beans xmlns="http://www.springframework.org/schema/beans"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns:context="http://www.springframework.org/schema/context"
            xmlns:ehcache="http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring"
            xmlns:util="http://www.springframework.org/schema/util"
            xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context-3.0.xsd
            http://www.springframework.org/schema/util
            http://www.springframework.org/schema/util/spring-util-3.0.xsd
            http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring
            http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring/ehcache-spring-1.1.xsd">

    <ehcache:annotation-driven cache-manager="ehCacheManager"/>
    <bean id="ehCacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>

This allows us to use the @Cacheable and @TriggersRemove annotations that come from google.
I then placed ehcache.xml in my classpath (Putting it in WEB-INF didnt work)

My ehcache.xml looked like this:

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
    <diskStore path="java.io.tmpdir/ClavisCaches"/>
    <cache name="personCache" maxElementsInMemory="9000" eternal="true" overflowToDisk="false"/>
</ehcache>


By setting the eternal value to true, this cache never expires unless I specifically trigger remove (see below). There are other ways to have the cache expire on a time limit. Please see the ehcache docs for this.

Then I created a Cache class that returned the Collection object that I wanted to cache:


import com.googlecode.ehcache.annotations.Cacheable;
import com.googlecode.ehcache.annotations.TriggersRemove;

...

@Cacheable(cacheName = "personCache")
public Collection<CacheElement> getPersonCacheSet() {

    List<Person> personList = wsClient.getSomeExpensiveCall();
    Collection<CacheElement> personCacheSet = new HashSet<CacheElement>(personList.size());

    for (Person person : personList) {
        CacheElement cacheElement = new CacheElement();
        cacheElement.setName(person.getName());
        cacheElement.setMobil(person.getMobil());
        personCacheSet.add(cacheElement);
    }

    return personCacheSet;
}

@TriggersRemove(cacheName = "personCache", when = When.AFTER_METHOD_INVOCATION, removeAll = true)
public void clearPersonCache() {
    // Intentionally blank
}

Note that the cacheable methods need to be called from a different class, because of the way the proxy is set up.

In my Spring configuration, I annotated this Cache class as a Service. This means Spring took care of initializing this class for me as a singleton on startup, so I could just autowire it and call the methods when needed.

Here is my jUnit file:


    @Autowired
    private PersonCache personCache;

    @Test
    public void testGetPersonCacheSet() {

        // The first call makes the expensive call, the second call gets the result from the cache
        Collection<CacheElement> personCacheSet0 = personCache.getPersonCacheSet();
        Collection<CacheElement> personCacheSet1 = personCache.getPersonCacheSet();

        assertSame(dealerCacheSet0, dealerCacheSet1);
    }

    @Test
    public void testClearCache() {

        // fill the cache
        personCache.getPersonCacheSet();

        // The first call gets the result from the cache
        Collection<PersonCacheElement> personCacheSet0 = personCache.getPersonCacheSet();

        personCache.clearPersonCache();

        // second call makes the expensive call
        Collection<CacheElement> personCacheSet1 = personCache.getPersonCacheSet();

        assertNotSame(personCacheSet0, personCacheSet1);
    }

No comments:

Post a Comment