Reutilización de configuración de cache: Spring, Hibernate y EhCache


Desde hace un buen tiempo es conocido el beneficio y los cuidados necesarios del (ab)uso de sistemas de Cache en nuestras aplicaciones. Hibernate soporta el uso de varios proveedores de caché (en la inminente versión 3.5 se incluirá un nuevo jugador a la lista, el muy interesante Infinispan, que a juzgar por el benchamark sobre aplicaciones locales publicado en su blog es muuuy eficiente -cerca de un 20% más que EhCache 1.7-, sin embargo aún no he encontrado una comparación contra el reciente EhCache 2.0 y la cantidad de dependencias de Infinispan es realmente grande).

Sin embargo, el asunto con la configuración del cache en Hibernate 3.3+ es que se debe implementar la interfaz RegionFactory -en lugar de la deprecated CacheProvider (usada hasta Hibernate 3.2)- y la implementación de EhCache 2.0 de esa interfaz define un CacheManager protegido que es instanciado en el método start cuando el SessionFactory de Hibernate es creado.

Esa implementación evita que se reutilice la instancia del CacheManager (el cual es algo costoso en términos de creación de instancia, en la documentación se recomienda que sea un singleton) y la configuración del mismo (archivos *ehcache*.xml), asi que para un proyecto en el que se quiera usar cache, tendriamos al menos dos intancias de CacheManager, para evitar eso y centralizar la configuración, vamos a implementar un RegionFactory con base en EhCache y usando un truco descrito por Les Hazlewood (Recomiendo mucho su lectura) para la implementación usable hasta Hibernate 3.2.

El código de la clase InjectedEhCacheRegionFactory incluye la validación del cache encontrado en la clase HibernateUtil de EhCache y es realmente sencillo (los comentarios en los métodos han sido eliminados para permitir la legibilidad en WP):

public class InjectedEhCacheRegionFactory implements RegionFactory {
	/**
	 * Logger for this class
	 */
	private static final Logger logger = Logger
			.getLogger(InjectedEhCacheRegionFactory.class);
	private static CacheManager cacheManager = null;
	/**
     * MBean registration helper class instance for Ehcache Hibernate MBeans.
     */
    protected final ProviderMBeanRegistrationHelper mbeanRegistrationHelper = new ProviderMBeanRegistrationHelper();
    
	/**
	 * @param cacheManager the cacheManager to set
	 */
	public static void setCacheManager(CacheManager cacheManager) {
		InjectedEhCacheRegionFactory.cacheManager = cacheManager;
	}
	
	public InjectedEhCacheRegionFactory(Properties properties) {
		super();
	}
	
	public CollectionRegion buildCollectionRegion(String regionName,
			Properties properties, CacheDataDescription metadata)
			throws CacheException {
		return new EhcacheCollectionRegion(getCache(regionName), null, metadata, properties);
	}

	
	public EntityRegion buildEntityRegion(String regionName,
			Properties properties, CacheDataDescription metadata)
			throws CacheException {
		return new EhcacheEntityRegion(getCache(regionName), null, metadata, properties);
	}

	public QueryResultsRegion buildQueryResultsRegion(String regionName,
			Properties properties) throws CacheException {
		return new EhcacheQueryResultsRegion(getCache(regionName), properties);
	}

	public TimestampsRegion buildTimestampsRegion(String regionName,
			Properties properties) throws CacheException {
		return new EhcacheTimestampsRegion(getCache(regionName), properties);
	}

	public boolean isMinimalPutsEnabledByDefault() {
		return false;
	}

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

	public void start(Settings settings, Properties properties)
			throws CacheException {
        try {
            mbeanRegistrationHelper.registerMBean(cacheManager, properties);
        } catch (net.sf.ehcache.CacheException e) {
            throw new CacheException(e);
        }
	}

	public void stop() {
	}
	
	private Ehcache getCache(String name) throws CacheException {
        try {
            Ehcache cache = cacheManager.getEhcache(name);
            if (cache == null) {
                logger.warn("Couldn't find a specific ehcache configuration for cache named [" + name + "]; using defaults.");
                cacheManager.addCache(name);
                cache = cacheManager.getEhcache(name);
                logger.debug("started EHCache region: " + name);
            }
            validateHibernateCache(cache);
            return cache;
        } catch (net.sf.ehcache.CacheException e) {
            throw new CacheException(e);
        }
    }
	
	private void validateHibernateCache(Ehcache cache) {
		CacheConfiguration cacheConfig = cache.getCacheConfiguration();

        if (cacheConfig.isTerracottaClustered()) {
            TerracottaConfiguration tcConfig = cacheConfig.getTerracottaConfiguration();
            switch (tcConfig.getValueMode()) {
                case IDENTITY:
                    throw new CacheException("The clustered Hibernate cache " + cache.getName() + " is using IDENTITY value mode.\n"
                           + "Identity value mode cannot be used with Hibernate cache regions.");
                case SERIALIZATION:
                default:
                    // this is the recommended valueMode
                    break;
            }
        }
	}
}

Ahora bien, la configuración de la instancia de CacheManager, la inyección en la clase InjectedEhCacheRegionFactory y la configuración consecuente del SessionFactory siguen las indicaciones de Les para un contexto de Spring (supongo que en Guice u otras herramientas similares debe ser bastante cercano a esto):

<bean id="ehcacheCacheManager" class="net.sf.ehcache.CacheManager" destroy-method="shutdown"
    	scope="singleton">
	    <constructor-arg type="java.net.URL" value="classpath:/ehcache.xml"/>
	</bean>
	
	<bean id="cacheProviderCacheManagerInjector"
         class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="staticMethod" value="com.alephsa.spring.InjectedEhCacheRegionFactory.setCacheManager"/>
    <property name="arguments">
        <list>
            <ref bean="ehcacheCacheManager"/>
        </list>
    </property>
</bean>
	
    <!-- Hibernate SessionFactory -->
    <bean id="sessionFactory" depends-on="cacheProviderCacheManagerInjector"
        class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="hibernateProperties">
            <props>
                <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">com.alephsa.spring.InjectedEhCacheRegionFactory</prop>
                <prop key="current_session_context_class">thread</prop>
                <prop key="hibernate.dialect">${jdbc.sql.dialect}</prop>
		        <prop key="hibernate.show_sql">${jdbc.sql.show}</prop>
                <prop key="hibernate.format_sql">${jdbc.sql.format}</prop>
                <prop key="hibernate.hbm2ddl.auto">${jdbc.sql.create}</prop>
            </props>
        </property>
    </bean>

Espero que sea de ayuda.

Acerca de Nickman

Aunque crítico e Ingeniero (especializado en software), piloto de aeroplano soy (seré).

Un pensamiento en “Reutilización de configuración de cache: Spring, Hibernate y EhCache

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s