Combining Hibernate Cache and Ehcache for Better Java Scalability

With advanced load balancers and decreasing hardware costs, ensuring the scalability of enterprise applications across multiple boxes is becoming more economically feasible. Ideally, an application's TPS (transactions per second) will rise in a linear fashion as the number of servers it runs on increases. However, sooner or later you will encounter performance issues on one box that you can't easily (or at least cheaply) alleviate by scaling across multiple servers, such as your relational database for example.

Hence, it is critical to lower your database load. In particular, you can employ efficient caching strategies that reduce the number of SQL queries and the amount of data that needs to be transferred over the wire. One powerful caching strategy that meets these requirements is to combine Hibernate cache (second-level) and Ehcache.

In this article I explain how to apply this strategy -- along with the Spring Framework -- to improve the scalability of your enterprise Java applications. I provide source code for a demo application as well.

Hibernate Second-level Cache and Ehcache

A typical Web request is wrapped around a filter (e.g. a Spring Hibernate session filter) that automatically opens and closes a Hibernate session during its duration. The Spring OpenSessionInViewFilter is an example of such as filter. Within each session, Hibernate maintains a first-level cache to ensure against multiple copies of the same entity within that session. This cache is discarded as soon as the session closes, and it is not persistent.

However, Hibernate also offers a second-level cache, which is persistent. It is designed with the capability to cache serialized copies of entities over longer periods of time. Hibernate's cache mechanism is pluggable and various caching libraries offer integration modules for it. Among these, the most popular and robust is by far Ehcache -- an extremely powerful caching product.

Using a local in-memory/disk cache can not only lower the load on the database, but it can also dramatically increase read speeds. In an internal test we ran at my organization, we were able to get a throughput of 20,000+ reads per second for a Hibernate entity from a local Ehcache disk cache. Retrieving it directly from the database maxed out at 5000 reads per second -- a significant difference.

Ehcache can store large collections of objects in memory or automatically overflow them to disk. It also provides control over many aspects of the caching mechanism, including along others:

How many objects you want in memory

How long before the objects become stale and need to be automatically refreshed

The hibernate.cache.region.factory_class property configures Ehcache as the second-level cache provider. The net.sf.Ehcache.configurationResourceName property tells Ehcache the classpath location of the configuration file that fine-tunes its setting. Note that this file must be in the classpath, hence it's usually embedded in the JAR/WAR file itself.

Fine-tuning Ehcache Settings

Here's our sample com.developer.ehcache.xml configuration file from the setup in the previous section:

Ehcache creates a separate cache for every Hibernate entity, with a name equivalent to its full path. Hence, you can override every entity's cache settings if the need arises. However, in most cases the defaultCache settings are sufficient as a template. In this example we've chosen settings to:

cache no more than 10,000 instances of a single entity in memory

force the entity to refresh from the database every 120 seconds

let it overflow to disk if we reach more than our maxElementsInMemory limit

Configuring Hibernate/JPA Entities

Just because we've configured Hibernate and Ehcache together, that does not mean that every single entity is now automatically cached. Every single Hibernate entity must be explicitly flagged via the @Cache annotation:

The Hibernate documentation explains the different cache concurrency strategies, but NONSTRICT_READ_WRITE is the most common one. If you are running in a full JTA environment, you may choose TRANSACTIONAL instead.