Spring Hibernate-3 2nd Level EHCache 2.8 Configurations with C3P0 Pooling

Here I am discussing the configurations required to enable a 2nd Level caching using EHCache in a Spring Hibernate 3 ORM setup.

Pre-Requisites: You should have a working hibernate project in order to migrate to EHCache with in-memory and local disk type of caching configuration. Be aware that this implementation uses RAM to maintain the cached collections and should not be handy in case of a large dynamic load. You should move to distributed cache using a Terracotta server arrays or a totally different implementation using the AWS Elastic Cache.

Maven Dependencies:

 <dependency>
 <groupId>org.hibernate</groupId>
 <artifactId>hibernate-c3p0</artifactId>
 <version>${org.hibernate-version}</version>
 </dependency>

 <!-- EHCache Dependencies -->
 <dependency>
 <groupId>net.sf.ehcache</groupId>
 <artifactId>ehcache</artifactId>
 <version>2.8.0</version>
 </dependency>

 <dependency>
 <groupId>org.hibernate</groupId>
 <artifactId>hibernate-ehcache</artifactId>
 <version>${org.hibernate-version}</version>
 </dependency>

Step 1:  Make sure your hibernate session factory configuration looks like the following,

<bean id="c3p0DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
 <property name="driverClass" value="${jdbc.driverClassName}" />
 <property name="jdbcUrl" value="${jdbc.url}" />
 <property name="user" value="${jdbc.username}" />
 <property name="password" value="${jdbc.password}" />
 <property name="minPoolSize" value="2" />
 <property name="maxPoolSize" value="50" />
 <property name="maxIdleTime" value="319" />
 </bean>
<bean id="mySessionFactory"
 class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
 <property name="dataSource" ref="c3p0DataSource" />
 <property name="annotatedClasses">
 <list>
 <value>com.xyz.abc.MyBean1</value>
 <value>com.xyz.abc.MyBean2</value>
 </list>
 </property>
 <property name="hibernateProperties">
 <props>
 <prop key="hibernate.dialect">${hibernate.dialect}</prop>
 <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
 <prop key="hibernate.generate_statistics">${hibernate.generate_statistics}</prop>
 <prop key="hibernate.connection.useUnicode">true</prop>
 <prop key="hibernate.connection.characterEncoding">UTF-8</prop>
 <!-- EHCache Configuration -->
 <prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</prop>
 <prop key="hibernate.cache.use_second_level_cache">true</prop>
 <prop key="hibernate.cache.use_query_cache">true</prop>
 <prop key="hibernate.cache.region.factory_class">net.sf.ehcache.hibernate.SingletonEhCacheRegionFactory</prop>
 </props>
 </property>
 </bean>
 <bean id="transactionManager"
 class="org.springframework.orm.hibernate3.HibernateTransactionManager">
 <property name="sessionFactory" ref="mySessionFactory" />
 </bean>
<context:annotation-config />
<tx:annotation-driven />

From the above config you can be sure that the two domain objects that are being configured with the session factory are using a pooled connection from c3po and have second level cache and query cache enabled. We also observe that the transactions are annotation driven so we dont have to worry about manually doing a begin and commit on any of them.

Step 2: EHCache Configuration file in class path <ehcache.xml>

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
    maxBytesLocalHeap="100M"
    maxBytesLocalDisk="2G">

 <sizeOfPolicy maxDepth="100000" maxDepthExceededBehavior="abort"/>

 <defaultCache eternal="false" 
    timeToIdleSeconds="120" 
    timeToLiveSeconds="120"
    memoryStoreEvictionPolicy="LRU" 
    statistics="true">
    <persistence strategy="localTempSwap"/>
 </defaultCache>

 <cache name="org.hibernate.cache.StandardQueryCache"
    eternal="false" 
    timeToIdleSeconds="120"
    timeToLiveSeconds="120">
    <persistence strategy="localTempSwap"/>
 </cache>
</ehcache>

This configuration file is looked up by the cache configuration reader. We have enabled default cache for all the beans and a separate cache for the Queries. Both of these cache will share the resources pooled in the ehcache attributes. Example local heam of 100 MB will be shared equally by all cache.

Step 3: Annotate the Domains with the relevant caching annotation

@Entity
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@Table(name = "MyTable1")
public class MyBean1 {

...

Step 4: Make sure you have annotation config in you app context

<context:component-scan base-package="com.xyz.abc, com.xyz.def"/>
<context:annotation-config/>

Step 5: Finally, Make sure your DAO is setting the cache store. This can be set to true in the query creation of session call. Find the relevant examples below.

public Collection<T> getAll() throws DataAccessException {
 return _sessionFactory.getCurrentSession()
 .createCriteria(this._persistentClass).setCacheable(true).list();
 }
public Collection<T> findBySQLQuery(String sqlQueryString)
 throws DataAccessException {
 return _sessionFactory.getCurrentSession()
 .createSQLQuery(sqlQueryString).setCacheable(true).list();
 }
public Collection<T> findByHSQLQuery(String hsqlQueryString,
 Map<String, Object> map) {
 return _sessionFactory.getCurrentSession().createQuery(hsqlQueryString)
 .setCacheable(true).setProperties(map).list();
}
public Collection<T> findByHSQLNamedQuery(String namedQuery,
 Map<String, Object> map) {
 return _sessionFactory.getCurrentSession().getNamedQuery(namedQuery)
 .setCacheable(true).setProperties(map).list();
 }

This should be helpful in terms of code required to wire up the caching in the project.

Test: A way to test is by switching on the hibernate logging and making a get all query call multiple times. The first time the query should make a sql query connect to the db and respond while the other times it should just return from the local cache without this query to db.

Configure the following in log4j.xml

<category name="org.hibernate" additivity="false">
 <priority value="INFO" />
 <appender-ref ref="console" />
 </category>