Spring JDBC and Ehcache

A couple of months ago I started refactoring a client’s code to bring it up to current standards. The client had three projects talking to the same database three different ways: MyBatis, Hibernate, and Spring JDBC. Also, two of the projects were still using ANT. I set out to switch all the projects to Maven, Spring, and a single, shared DAO layer using Spring JDBC.

One of the client’s projects exports millions of rows of data. Small changes can greatly affect export time.┬áThere was one heavily called block of code that used a HashMap in an instance variable to cache some objects. I decided to move caching into the DAO instead. That change caused the export to run three times longer than before. Why was Ehcache so slow? Had I done something wrong?

I set-up a test project to measure how long it took to randomly read 10,000 rows from a 340-row table. Here are the results.

Cache Performance

Cache Performance

It took 0.043 seconds to perform 10,000 random reads from a HashMap of 340 objects. By contrast, it took almost 27 seconds to fetch the same set of 10,000 random keys directly from MySQL. The H2 database only took 0.58 seconds. The elapsed times were about the same using Ehcache backed by either H2 or MySQL, taking 2.36 seconds and 2.42 seconds, respectively.

Example Project on GitHub

To experiment with my configuration I created a simple test project that has H2 Database and MySQL. You can find the code on GitHub if you want to run the tests yourself.

https://github.com/smitchell/SpringJdbcEcache

Setting up Spring JDBC and Ehcache

I used annotations to set-up Spring JDBC and Ehcache.

Here is how I set-up the cache in the Spring context.

    <bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"
          p:configLocation='ehcache.xml'
          p:shared="true" />
    <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cacheManager-ref="ehcache" />
    <cache:annotation-driven />

Here is an example from the Country DAO in the sample project.

@Repository
public class CountryDaoImpl implements CountryDao {

    @Cacheable("countries")
    public Country findByPrimaryKey(final Long id) {
        final MapSqlParameterSource params = new MapSqlParameterSource("value", id);
        final String sql = selectSQL.concat(" where ").concat(CountryColumn.COUNTRY_ID.name()).concat(" = :value");
        final List results = jdbcTemplate.query(sql, params, countryRowMapper);
        if (results.size() > 0) {
            return results.get(0);
        }
        return null;
    }

    @CacheEvict(value = "countries", allEntries=true)
    public void save(Country instance) {
        final Date now = new Date();
        instance.setLastUpdateDate(now);
        if (instance.getId() ==  null) {
            instance.setCreateDate(now);
            insert(instance);
        } else {
            update(instance);
        }
    }
...
}

The Ehcache configuration was equally straightforward.

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="true" monitoring="autodetect" dynamicConfig="true">
    <diskStore path="java.io.tmpdir"/>
    <defaultCache maxEntriesLocalHeap="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120"
                  diskSpoolBufferSizeMB="30" maxEntriesLocalDisk="10000000" diskExpiryThreadIntervalSeconds="120"
                  memoryStoreEvictionPolicy="LRU">
        <persistence strategy="localTempSwap"/>
    </defaultCache>
    <cache name='countries' maxElementsInMemory='400' eternal='true' overflowToDisk='false' />

</ehcache>

Conclusions

I’m still figuring out how best to leverage Ehcache, but it is clear that for small collections of simple objects, like categories, types, countries, states, roles, etc, Ehcache is overkill. For those types of objects, I will add a HashMap to my service object to act as a simple cache.

The place for Ehcache will probably turn out to be those compound object graphs assembled from calls to multiple tables. I’m going selectively leverage Ehcache in my service (@Component) implementations to hang on to those complex, composite objects.

If I succeed, I’ll add a follow-up to this post.

Advertisements