Hibernate Second Level Cache Using Ehcache


Put aside all the good and bad things about hibernate second-level cache, Personal Capital has been using second-level cache for the entities and queries of data that merely change. We had our issues while using it. But once we had these issues behind us, it really performs well for us.

How to enable in Hibernate

There are two types of second-level cache we use. One is entity cache, where hibernate caches loaded data objects on the Session Factory level, crossing user and session boundary, and crossing transaction boundary; Another type is query cache, where SQL query is the cache key and the query result set is the cached value. Whenever there is a same SQL query is submitted through hibernate, hibernate will try to use the cached result set instead of dipping into the database.

To globally set these caching up, just need to define a few hibernate properties. We use JPA, the following properties need to be defined in JPA persistence.xml.

hibernate.cache.use_query_cache true
hibernate.cache.use_second_level_cache true
hibernate.cache.region.factory_class org.hibernate.cache.ehcache.EhCacheRegionFactory
hibernate.cache.use_structured_entries true

 How to enable second-level caching for data objects in application

Hibernate provides a @Cache annotation to enable second-level caching for data objects in application.

This annotation can be used on @Entity class to enable second-level cache on the table level. For example,

@Table(name = “account_type”, catalog = “sp_schema”)
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class AccountTypeImpl extends AbstractTableWithDeleteImpl implements AccountType, Serializable
… …

it can also be used on @Column property level to enable

@Table(name = “account”, catalog = “sp_schema”)
public class AccountImpl extends AbstractTableWithDeleteImpl implements AccountType, Serializable
… …
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public String getAccountDetail()
… …

How to enable second-level caching for query cache in application

Query cache is different from data object cache. To enable query cache, you need to tell that to the query. In JPA query, a cache hint should be set to enable query cache.

query.setHint(“org.hibernate.cacheable”, true);

How to configure ehcache for data object second-level cache

When ehcache is used for second-level cache, hibernate tries to work with caches named after the fully qualified names of Domain Objects. Only if there is no matching cache name for a domain object, it tries to cache/retrieve this domain object using defaultCache.

So ehcache’s defaultCache should be configured carefully since any @Cache annotated domain objects will be cached in this cache, if no cache is configured using these domain objects’ fully qualified names.

All ehcache’s cache names can be defined and configured in ehcache.xml. ehcache.xml must be on application’s class path. A simple example;

<ehcache updateCheck=”false”><diskStore path=”java.io.tmpdir/hibernateCache” /><defaultCache maxElementsInMemory=”100″ eternal=”false” timeToIdleSeconds=”1″ timeToLiveSeconds=”1″ overflowToDisk=”false” diskPersistent=”false” diskExpiryThreadIntervalSeconds=”1″ memoryStoreEvictionPolicy=”LRU” /><cache name=”com.personalcapital.aggregation.data.impl.AccountTypeImpl”
maxEntriesLocalHeap=”1000″ eternal=”false” timeToIdleSeconds=”1200″
timeToLiveSeconds=”1200″ overflowToDisk=”false”
diskExpiryThreadIntervalSeconds=”60″ memoryStoreEvictionPolicy=”LRU” />

How to configure ehcache for second-level query cache

For second-level query cache, Hibernate uses caches named as “StandardQueryCache” and “UpdateTimestampsCache” to cache query result sets. If these caches are not configured in ehcache.xml, Hibernate will fall back to use “defaultCache” to cache query result sets, which may not be what is expected.

A note. The full qualified StandardQueryCache names are different in Hibernate 3 and 4. In Hibernate 3, the name is “org.hibernate.cache.StandardQueryCache”; and in Hibernate 4, the name is “org.hibernate.cache.internal.StandardQueryCache”. This was one thing had bite us when we were upgrading to Hibernate 4. The following is an example for Hibernate 4 ehcache.xml configuration for query cache.

“UpdateTimestampsCache” is really important too. Here is an excerpt from ehcache online document (http://ehcache.org/documentation/user-guide/hibernate#enable-second-level-cache-and-query-cache-settings), describing what “UpdateTimestampsCache” is: “Tracks the timestamps of the most recent updates to particular tables. It is important that the cache timeout of the underlying cache implementation be set to a higher value than the timeouts of any of the query caches. In fact, it is recommend that the the underlying cache not be configured for expiry at all.

… …<cache name=”org.hibernate.cache.internal.StandardQueryCache” maxEntriesLocalHeap=”2000″ eternal=”false” timeToIdleSeconds=”0″ timeToLiveSeconds=”300″ overflowToDisk=”false” diskExpiryThreadIntervalSeconds=”60″ memoryStoreEvictionPolicy=”LRU” /><cache name=”org.hibernate.cache.spi.UpdateTimestampsCache” maxEntriesLocalHeap=”5000″ eternal=”true” />… …

Native Query and Query Cache

Result set of native query can be cached in query cache the same way as result set of regular HQL query, you just need to tell that to the query. In JPA, the same cache hint should be set to enable query cache for native query.

query.setHint(“org.hibernate.cacheable”, true);

But there is a drawback to second-level cache when using native query. Because it is not HQL, Hibernate has no idea what data objects are affected by the native query, so it invalidates any cached data objects by default. This behavior usually is not desired. In order to avoid this global invalidation, the data entity is affected by the native query should be indicated before the query execution, so the query will only invalidate data objects of the indicated entity. If nothing is affected by the query and don’t want to invalidate anything, any empty query space should be specified.


Some ending words

Again, Hibernate second-level cache can turn into a monster if is not applied carefully in application. Caching strategy should be thought out carefully before applying to application. Otherwise, the application may have all kinds of unexpected behaviors. What are described above only cover the caching strategy we are using, basically it is a simple time-to-live timeout-bound caching. This is good for our seed data that have small data set and don’t change frequently.