Thursday 18 July 2013

Spring 3.x + Hibernate 4.x consistent cache configuration

The following sections describe a solution to unify caching in a Spring and Hibernate (2nd level cache) application. My solution, which works with Hibernate 4, is based on the code presented in the following article which is related to Hibernate 3:

http://leshazlewood.com/2007/12/06/spring-cache-configuration-hibernate-ehcache-et-al/

As explained in that article, the main benefits of this approach are:
  1. EhCache is used for both Spring and Hibernate caching
  2. Caching is initialized and configured through Spring in a single place and a single cache manager is used to manage all caches consistently.
This is the updated code that works with Hibernate 4.x and Spring 3.x. It uses code from Hibernate 4, therefore it's released under the GNU Lesser General Public License (LGPL) as the original.


 /*  
  * Hibernate, Relational Persistence for Idiomatic Java  
  *  
  * Copyright (c) 2011, Red Hat Inc. or third-party contributors as  
  * indicated by the @author tags or express copyright attribution  
  * statements applied by the authors. All third-party contributions are  
  * distributed under license by Red Hat Inc.  
  *  
  * This copyrighted material is made available to anyone wishing to use, modify,  
  * copy, or redistribute it subject to the terms and conditions of the GNU  
  * Lesser General Public License, as published by the Free Software Foundation.  
  *  
  * This program is distributed in the hope that it will be useful,  
  * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY  
  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License  
  * for more details.  
  *  
  * You should have received a copy of the GNU Lesser General Public License  
  * along with this distribution; if not, write to:  
  * Free Software Foundation, Inc.  
  * 51 Franklin Street, Fifth Floor  
  * Boston, MA 02110-1301 USA  
  */  
 package lb.cache;  
   
   
 import java.net.URL;  
 import java.util.Properties;  
   
 import net.sf.ehcache.CacheManager;  
 import net.sf.ehcache.Ehcache;  
 import net.sf.ehcache.util.ClassLoaderUtil;  
 import org.hibernate.cache.ehcache.EhCacheMessageLogger;  
 import org.jboss.logging.Logger;  
   
 import org.hibernate.cache.CacheException;  
 import org.hibernate.cache.ehcache.internal.nonstop.NonstopAccessStrategyFactory;  
 import org.hibernate.cache.ehcache.internal.regions.EhcacheCollectionRegion;  
 import org.hibernate.cache.ehcache.internal.regions.EhcacheEntityRegion;  
 import org.hibernate.cache.ehcache.internal.regions.EhcacheNaturalIdRegion;  
 import org.hibernate.cache.ehcache.internal.regions.EhcacheQueryResultsRegion;  
 import org.hibernate.cache.ehcache.internal.regions.EhcacheTimestampsRegion;  
 import org.hibernate.cache.ehcache.internal.strategy.EhcacheAccessStrategyFactory;  
 import org.hibernate.cache.ehcache.internal.strategy.EhcacheAccessStrategyFactoryImpl;  
 import org.hibernate.cache.ehcache.internal.util.HibernateUtil;  
 import org.hibernate.cache.ehcache.management.impl.ProviderMBeanRegistrationHelper;  
 import org.hibernate.cache.spi.CacheDataDescription;  
 import org.hibernate.cache.spi.CollectionRegion;  
 import org.hibernate.cache.spi.EntityRegion;  
 import org.hibernate.cache.spi.NaturalIdRegion;  
 import org.hibernate.cache.spi.QueryResultsRegion;  
 import org.hibernate.cache.spi.RegionFactory;  
 import org.hibernate.cache.spi.TimestampsRegion;  
 import org.hibernate.cache.spi.access.AccessType;  
 import org.hibernate.cfg.Settings;  
 import org.hibernate.service.classloading.spi.ClassLoaderService;  
 import org.hibernate.service.spi.InjectService;  
   
 public class ExternalEhCacheRegionFactory implements RegionFactory {  
   
   private static volatile CacheManager manager = 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 setManager(CacheManager cacheManager) {  
     ExternalEhCacheRegionFactory.manager = cacheManager;  
   }  
   
   public void start(Settings settings, Properties properties) throws CacheException {  
   //ignored, CacheManager lifecycle handled by the IoC container  
   }  
   
   public void stop() {  
   //ignored, CacheManager lifecycle handled by the IoC container  
   }  
   
   
   /**  
    * The Hibernate system property specifying the location of the ehcache configuration file name.  
    * <p/>  
    * If not set, ehcache.xml will be looked for in the root of the classpath.  
    * <p/>  
    * If set to say ehcache-1.xml, ehcache-1.xml will be looked for in the root of the classpath.  
    */  
   public static final String NET_SF_EHCACHE_CONFIGURATION_RESOURCE_NAME = "net.sf.ehcache.configurationResourceName";  
   
   private static final EhCacheMessageLogger LOG = Logger.getMessageLogger(  
       EhCacheMessageLogger.class,  
       ExternalEhCacheRegionFactory.class.getName()  
   );  
   
   /**  
    * MBean registration helper class instance for Ehcache Hibernate MBeans.  
    */  
   protected final ProviderMBeanRegistrationHelper mbeanRegistrationHelper = new ProviderMBeanRegistrationHelper();  
   
   /**  
    * Settings object for the Hibernate persistence unit.  
    */  
   protected Settings settings;  
   
   /**  
    * {@link EhcacheAccessStrategyFactory} for creating various access strategies  
    */  
   protected final EhcacheAccessStrategyFactory accessStrategyFactory =  
       new NonstopAccessStrategyFactory( new EhcacheAccessStrategyFactoryImpl() );  
   
   /**  
    * Whether to optimize for minimals puts or minimal gets.  
    * <p/>  
    * Indicates whether when operating in non-strict read/write or read-only mode  
    * Hibernate should optimize the access patterns for minimal puts or minimal gets.  
    * In Ehcache we default to minimal puts since this should have minimal to no  
    * affect on unclustered users, and has great benefit for clustered users.  
    * <p/>  
    * This setting can be overridden by setting the "hibernate.cache.use_minimal_puts"  
    * property in the Hibernate configuration.  
    *  
    * @return true, optimize for minimal puts  
    */  
   public boolean isMinimalPutsEnabledByDefault() {  
     return true;  
   }  
   
   /**  
    * {@inheritDoc}  
    */  
   public long nextTimestamp() {  
     return net.sf.ehcache.util.Timestamper.next();  
   }  
   
   /**  
    * {@inheritDoc}  
    */  
   public EntityRegion buildEntityRegion(String regionName, Properties properties, CacheDataDescription metadata)  
       throws CacheException {  
     return new EhcacheEntityRegion( accessStrategyFactory, getCache( regionName ), settings, metadata, properties );  
   }  
   
   @Override  
   public NaturalIdRegion buildNaturalIdRegion(String regionName, Properties properties, CacheDataDescription metadata)  
       throws CacheException {  
     return new EhcacheNaturalIdRegion( accessStrategyFactory, getCache( regionName ), settings, metadata, properties );  
   }  
   
   /**  
    * {@inheritDoc}  
    */  
   public CollectionRegion buildCollectionRegion(String regionName, Properties properties, CacheDataDescription metadata)  
       throws CacheException {  
     return new EhcacheCollectionRegion(  
         accessStrategyFactory,  
         getCache( regionName ),  
         settings,  
         metadata,  
         properties  
     );  
   }  
   
   /**  
    * {@inheritDoc}  
    */  
   public QueryResultsRegion buildQueryResultsRegion(String regionName, Properties properties) throws CacheException {  
     return new EhcacheQueryResultsRegion( accessStrategyFactory, getCache( regionName ), properties );  
   }  
   
   @InjectService  
   public void setClassLoaderService(ClassLoaderService classLoaderService) {  
     this.classLoaderService = classLoaderService;  
   }  
   
   private ClassLoaderService classLoaderService;  
   
   /**  
    * {@inheritDoc}  
    */  
   public TimestampsRegion buildTimestampsRegion(String regionName, Properties properties) throws CacheException {  
     return new EhcacheTimestampsRegion( accessStrategyFactory, getCache( regionName ), properties );  
   }  
   
   private Ehcache getCache(String name) throws CacheException {  
     try {  
       Ehcache cache = manager.getEhcache( name );  
       if ( cache == null ) {  
         LOG.unableToFindEhCacheConfiguration( name );  
         manager.addCache( name );  
         cache = manager.getEhcache( name );  
         LOG.debug( "started EHCache region: " + name );  
       }  
       HibernateUtil.validateEhcache( cache );  
       return cache;  
     }  
     catch ( net.sf.ehcache.CacheException e ) {  
       throw new CacheException( e );  
     }  
   
   }  
   
   /**  
    * Load a resource from the classpath.  
    */  
   protected URL loadResource(String configurationResourceName) {  
     URL url = null;  
     if ( classLoaderService != null ) {  
       url = classLoaderService.locateResource( configurationResourceName );  
     }  
     if ( url == null ) {  
       ClassLoader standardClassloader = ClassLoaderUtil.getStandardClassLoader();  
       if ( standardClassloader != null ) {  
         url = standardClassloader.getResource( configurationResourceName );  
       }  
       if ( url == null ) {  
         url = ExternalEhCacheRegionFactory.class.getResource( configurationResourceName );  
       }  
     }  
     if ( LOG.isDebugEnabled() ) {  
       LOG.debugf(  
           "Creating EhCacheRegionFactory from a specified resource: %s. Resolved to URL: %s",  
           configurationResourceName,  
           url  
       );  
     }  
     if ( url == null ) {  
   
       LOG.unableToLoadConfiguration( configurationResourceName );  
     }  
     return url;  
   }  
   
   /**  
    * Default access-type used when the configured using JPA 2.0 config. JPA 2.0 allows <code>@Cacheable(true)</code> to be attached to an  
    * entity without any access type or usage qualification.  
    * <p/>  
    * We are conservative here in specifying {@link AccessType#READ_WRITE} so as to follow the mantra of "do no harm".  
    * <p/>  
    * This is a Hibernate 3.5 method.  
    */  
   public AccessType getDefaultAccessType() {  
     return AccessType.READ_WRITE;  
   }  
   
 }  
   


The cache manager can be injected using Spring xml.


  <!-- Use Spring Cache manager in Hibernate -->  
  <bean id="cacheManagerInjector"  
   class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">  
   <property name="staticMethod" value="lb.cache.ExternalEhCacheRegionFactory.setManager"/>  
   <property name="arguments">  
    <list>  
     <ref bean="ehCacheManagerFactoryBean"/>  
    </list>  
   </property>  
  </bean>  
      


I suggest reading the original article as it is interesting and provides more information.

3 comments:

  1. Can you please post complete code, that will be very useful. Thanks in advance

    ReplyDelete
  2. Hi Luigi,
    Thanks for your effort. I think the code is not fully compatible with Hibernate 5.2.x

    For example HibernateUtil.validateEhcache(cache) is not found.

    ReplyDelete