Consistent Cache Configuration: Spring, Hibernate, EhCache, Shiro, et. al.

Have you ever wanted to use caching in your Spring/Hibernate application beyond just supporting Hibernate’s 2nd-level cache?

If you know what a Hibernate 2nd-level cache is, you really know how huge its performance benefits are. Wouldn’t it be useful to utilize caching for other things in your application? Even if you don’t explicitly use a caching API in your app, you still might already be using caching without knowing it – other open source frameworks in addition to Hibernate utilize caching internally for the same performance benefits.

This article explains how to achieve the following:

1) I know I’m going to utilize caching for Hibernate’s 2nd-level cache, but I want to enable that same caching support to other libraries and frameworks that might need it. It would also nice to have if I ever need to use it in my own application code directly.

2) My application is Spring-configured, so I want to configure that caching support in Spring like everything else and then have all the frameworks/libraries, including Hibernate, to use these Spring-configured cache beans.

Unfortunately Hibernate makes this a pain to do.

Hibernate uses something called a CacheProvider to support its 2nd-level cache. A Hibernate SessionFactory uses a single CacheProvider to maintain/manage the individual Cache instances used by the 2nd-level cache.

Now here’s the problem: This CacheProvider is instantiated by Hibernate based on text configuration properties specified to the SessionFactory. When the SessionFactory starts up, it reads these properties, finds the one called hibernate.cache.provider_class which specifies a fully-qualified class name of the CacheProvider implementation, and creates a new instance of that class via reflection. It then uses that instance from then on to manage the Cache instances it needs.

For existing spring configs, here is a simplified example (note the 2nd hibernate property):


class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">


hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
hibernate.cache.provider_class = net.sf.ehcache.hibernate.EhCacheProvider


Why is this a problem? Because you can’t easily configure your CacheProvider instance yourself and then just tell the SessionFactory to use it. The SessionFactory interface and its default implementation do not have setter methods that would allow you to do so. This means you can’t use Dependency-Injection to set the CacheProvider instance on the Hibernate SessionFactory in Spring or any other DI microcontainer.

This is a problem for me too. The scenario that prompted this article happens to me quite a bit – I use Hibernate plus other libraries that benefit from caching. My own open-source project, Apache Shiro, can use caching to greatly speed up security checks. When I use Hibernate and Shiro in the same application, I want both frameworks to use the same underlying cache infrastructure. And although Shiro supports DI for its cache support, Hibernate does not, which means I have to jump through hoops to get Hibernate to play nice.

Where does Ehcache come in to this? Well, Ehcache is one of the more well-known and stable open-source caching solution for enterprise applications. It is stable and has a long history, it is extremely efficient with goodies like smart multi-threading under the hood, and now with Ehcache 1.3+, supports multicast distributed caching in a cluster – a huge benefit in enterprise apps. Basically, for open-source options, it is one of the best you can get. Cheers to Greg Luck for creating and supporting this great library (Another wonderful Open Source alternative is TerraCotta, take a look at that too).

So, in this particular example, I’m using EhCache but I also want to use it not just for Hibernate, but for Shiro and everything else that could use a Cache. And until now, I’ve been using Ehcache as Hibernate’s cache provider using the aforementioned text property. Hibernate would create an instance of EhCacheProvider and use it internally. Basically this EhCacheProvider implementation is a wrapper around EhCache’s own API of a CacheManager.

The CacheManager is really what I want to configure in Spring. Typically it is good practice and much easier to specify a single CacheManager for an application, which uses a single ehcache.xml file for all configuration. Then you can use this one ehcache.xml file for your Hibernate cache configuration, as well as for any other library that would use EhCache under the hood.

So, since you can’t inject a CacheManager into Hibernate’s SessionFactory, how do you do this in Spring?

Well, Since Hibernate instantiates the CacheProvider itself, and we can’t inject anything, our only option left is via static memory. Blech, I know – I don’t like statics any more than you do, but don’t worry – it is _only_ used as a mechanism to make Hibernate play nice and its never used anywhere else in your config or application code.

*Note that static memory will only allow a single CacheManager to be used across the entire VM, so if you need more than one Hibernate SessionFactory, each with its own separate cache configs, you’ll need to do more trickery (maybe 2 static fields? one per SessionFactory? You’ll know more about your app than I do, so I’ll leave how to do that to you. But you can use this the following technique and expand on it for your environment).

Let’s get to the code, its only one class. You’ll create your own CacheProvider implementation to statically reference a CacheManager instance. The CacheManager instance will be configured in Spring, and we’ll get to that in a bit. Here’s the CacheProvider implementation:


package com.leshazlewood.hibernate;

import net.sf.ehcache.CacheManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.cache.Cache;
import org.hibernate.cache.CacheException;
import org.hibernate.cache.CacheProvider;
import org.hibernate.cache.Timestamper;

import java.util.Properties;

/**
* @author Les Hazlewood
*/
public class ExternalEhCacheProvider implements CacheProvider {

protected transient final Log log = LogFactory.getLog(getClass());

private static CacheManager cacheManager = null;

/**
* This is the method that is called by an external framework (e.g. Spring) to set the
* constructed CacheManager for all instances of this class. Therefore, when
* Hibernate instantiates this class, the previously statically injected CacheManager
* will be used for all hibernate calls to build caches.
* @param cacheManager the CacheManager instance to use for a HibernateSession factory using
* this class as its cache.provider_class.
*/
public static void setCacheManager(CacheManager cacheManager) {
ExternalEhCacheProvider.cacheManager = cacheManager;
}

public Cache buildCache(String name, Properties properties) throws CacheException {
try {
net.sf.ehcache.Ehcache cache = cacheManager.getEhcache(name);
if (cache == null) {
if ( log.isWarnEnabled() ) {
log.warn( "Unable to find EHCache configuration for cache named [" + name + "]. Using defaults.");
}
cacheManager.addCache( name );
cache = cacheManager.getEhcache(name);
if (log.isDebugEnabled()) {
log.debug("Started EHCache region '" + name + "'");
}
}
return new net.sf.ehcache.hibernate.EhCache(cache);
} catch (net.sf.ehcache.CacheException e) {
throw new CacheException(e);
}
}

public long nextTimestamp() {
return Timestamper.next();
}

public void start(Properties properties) throws CacheException {
//ignored, CacheManager lifecycle handled by the IoC container
}

public void stop() {
//ignored, CacheManager lifecycle handled by the IoC container
}

public boolean isMinimalPutsEnabledByDefault() {
return false;
}
}

Ok, now that you have this class, what do you do with it? Well the first thing is to change your hibernate.cache.provider_class value to now be com.leshazlewood.hibernate.ExternalEhCacheProvider in your Hibernate SessionFactory bean definition. Then you want to define your CacheManager instance as a bean in your Spring config. Next you want to tell this CacheProvider implementation to use that instance when Hibernate needs to use caching functions.

We do that via a cool little Spring trick – by using the MethodInvokingFactoryBean. Basically this bean will call a static method at application startup with specified method arguments. Its a handy little tool that we can use to statically call the setter method that makes the CacheProvider work:





class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">


So the above spring config says: At application startup, create the CacheManager instance named ‘ehcacheCacheManager’ and then inject it into the ExternalEhCacheProvider via that class’s ‘setCacheManager’ static method.

But are we finished?

Well, not quite. We have one piece of cleanup work to do.

The experienced Spring folks will recognize that there can possibly be a race condition with our Hibernate SessionFactory and CacheManager/CacheProvider definitions. Yes, our Spring-configured CacheManager instance will be created and injected into the ExternalEhCacheProvider class correctly, and our Hibernate SessionFactory bean instance will be created correctly. But, what if the Hibernate SessionFactory tries to use a ExternalEhCacheProvider instance before it is ready to be used? (i.e. before the static CacheManager property has been injected?)

To prevent this race condition and guarantee that the ExternalEhCacheProvider’s internal CacheManager instance is set so it is available when the Hibernate SessionFactory needs it, we use the spring bean ‘depends-on’ attribute in our SessionFactory definition:


class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"
depends-on="cacheProviderCacheManagerInjector">


hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
hibernate.cache.provider_class = com.leshazlewood.hibernate.ExternalEhCacheProvider


So, although our Hibernate SessionFactory bean doesn’t actually use the CacheManager as a property – otherwise this whole article would be pointless ;) – we force it to wait until the cacheProviderCacheManagerInjector bean has done its work before we allow it to initialize.

We make the ‘depends-on’ attribute point to this injector (MethodInvokingFactoryBean) instance specifically (as opposed to the ehcacheCacheManager bean) because we care that it is not only created, but also injected too.

There. Now we’re done. :)

Now when the Hibernate SessionFactory bean starts up and creates its hibernate.cache.provider_class instance, we can rest assured that it will access our Spring-configured CacheManager.

And, you’ll be able to use that same Spring-configured ‘ehcacheCacheManager’ bean and inject it into Shiro, or any other framework that supports simple Dependency Injection. Because this bean is an application singleton and uses a single config file, now all of your caching config across the entire application is easily manged from the same location – very convenient.

Have fun!

24 thoughts on “Consistent Cache Configuration: Spring, Hibernate, EhCache, Shiro, et. al.”

  1. Very useful. Thanks. I am working on a project where I need some access to Hibernate’s ehcache. So I used your technique and now I can invalidate required objects in cache and still leave all cache untouched with eternal set to true.

  2. Very useful indeed. I am going to use this to setup our distributed cache for hibernate entities and for our higher level distributed objects. Thanks for your time writing this down!

  3. Hi Vitaly,

    I want to know the details of invalidating the objects.. I am facing problems in reading the cache itself. I can see the values in eclipse debug mode but when I do the get method on the cache it is not returning null value. Please help me out.

    Thanks,
    Kittu.

  4. Nice hack, but you don’t have to make the CacheManager static. Use org.springframework.cache.ehcache.EhCacheManagerFactoryBean to set up a cache manager. Inject it to your custom CacheProvider, then inject the cacheProvider to org.springframework.orm.hibernate3.LocalSessionFactoryBean using the cacheProvider properties.

  5. I’m using spring and hibernate with ehcache.
    The cache is working properly when I look at the hybernate queries log trace. The problem is that I allways get a different instance of the cache object when I’d like to get the same instance.
    Is there any way to do it?
    Thanks
    Vincent

  6. @Anonymous Coward,

    This is true – a Spring-configured Hibernate SessionFactory will now accept an injected CacheProvider – but this functionality was only introduced recently in Spring 2.5.1. So the solution is valid for versions prior to that.

    But also, my solution is still a good one if you want to use a consistent caching infrastructure across your application – you wouldn’t want to inject a Hibernate CacheProvider into components that don’t use the Hibernate API.

    It is nice to have a wrapper CacheManager that is in your package structure that can be used everywhere – in Hibernate DAOs and other managers/services, without coupling everything to the Hibernate API.

  7. @Vincent

    If you’re talking about the data that Hibernate caches in the 2nd-level cache, then no, there is no way to guarantee the same instance every time.

    The reason is that Hibernate doesn’t store the objects themselves in the 2nd-level cache – it stores it in what they call a ‘dehydrated’ form – basically an array of data, or more accurately, a tuple.

    For example, let’s say you had a User class with two properties, username and password. If you tell Hibernate to cache Users, it doesn’t cache the actually User instance. It creates a tuple (i.e. array of just the properties – username and password). When the User is requested from the 2nd-level cache, Hibernate uses reflection to create a new User instance and then reflectively calls setUsername and then setPassword.

    So that’s why you won’t get the same User instance across Sessions. You will get the same instance back in the same Session though, because once it is ‘hydrated’ from the 2nd-level cache, hibernate puts it in the transient (transactionally bound) 1st-level cache and uses it for the remainder of the transaction.

  8. Hi, Les,
    I have used your code on my project. Thanks!
    I would ask a quesion here.
    You said, “Since Hibernate instantiates the CacheProvider itself, and we can’t inject anything, our only option left is via static memory.” Is it possible for this static declaration to cause memory leak? I have this concern because in ExternalEhCacheProvider class we have declared:
    private static CacheManager cacheManager = null;
    which is static; while in
    net.sf.ehcache.CacheManager
    we have a field:
    static java.util.List ALL_CACHE_MANAGERS
    The documentation says that “CacheManagers should remove themselves from this list during shut down.” Is this ensured?

  9. thank u very much for giving useful information about spring and hibernate cache.
    Provide more articles like this.

  10. Thank a lot very much for giving such a useful information about EHCache integration with Spring and Hibernate.

  11. i getting error ExternalEhCacheProvider.cacheManager is null?

    my hibernatesessionfactory configuration is like yours

    com.package.ExternalEhCacheProvider

    cacheManger is not injected into ExternalEhCacheProvider? what i did wrong?

  12. I am one of the authors of a new project intended to provide Ehcache integration for Spring 3 projects via annotations:

    http://code.google.com/p/ehcache-spring-annotations/

    We are excited to announce the general availability of the first production release, 1.0.1.

    This release provides 2 method-level annotations in the spirit of Spring’s @Transactional:

    @Cacheable
    @TriggersRemove

    When appropriately configured in your Spring application, this project will create caching aspects at runtime around your @Cacheable annotated methods.

    Usage documentation can be found on the project wiki:

    http://code.google.com/p/ehcache-spring-annotations/wiki/UsingCacheAnnotations
    http://code.google.com/p/ehcache-spring-annotations/wiki/UsingTriggersRemove

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>