miércoles, 18 de noviembre de 2009

Configuración de EhCache en Spring

En este artículo vamos a ver cómo se puede acceder a los datos almacenados en una base de datos a través de una plantilla JDBC de Spring. Para mejorar el rendimiento en este acceso a base de datos introduciremos también una caché usando EhCache que permita almacenar las consultas ya obtenidas evitando el acceso a la base de datos.

En primer lugar, creamos en Eclipse un proyecto Maven e incluimos las dependencias necesarias en el archivo pom.xml del proyecto:
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.roldan.spring</groupId>
<artifactId>PruebaCache</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
<version>2.5.6</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.10</version>
</dependency>
<dependency>
<groupId>org.springmodules</groupId>
<artifactId>spring-modules-cache</artifactId>
<version>0.7</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>1.6.2</version>
</dependency>
</dependencies>
</project>

Las dependencias necesarias para nuestro proyecto son:
  • El framework Spring.
  • Log4j para sacar logs informativos sobre la ejecución de la aplicación.
  • El conector a base de datos MySql.
  • El módulo de cache del proyecto spring-modules, que permite integrar la herramienta EhCache con Spring de forma sencilla.
  • La herramienta de cacheo EhCache.

Posteriormente hemos creado una base de datos (pruebacache) con una única tabla (elementos) cuyos elementos serán recogidos por las consultas que realiza la aplicación:

El objeto al que se mapean los registros de esta tabla está definido en la siguiente clase:
package com.roldan.spring.model;

public class Elemento {

private int id;
private String descripcion;
private int grupo;

public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getDescripcion() {
return descripcion;
}
public void setDescripcion(String descripcion) {
this.descripcion = descripcion;
}
public int getGrupo() {
return grupo;
}
public void setGrupo(int grupo) {
this.grupo = grupo;
}
}

A continuación se muestra el archivo de configuración de Spring, spring-config.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ehcache="http://www.springmodules.org/schema/ehcache"
xmlns:jdbc="http://www.springmodules.org/schema/jdbc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
http://www.springmodules.org/schema/ehcache
http://www.springmodules.org/schema/cache/springmodules-ehcache.xsd">
<bean id="datasource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost/pruebacache"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="datasource"/>
</bean>
<bean id="elementoDao" class="com.roldan.spring.dao.ElementoDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
<ehcache:config configLocation="classpath:ehcache.xml"/>
<ehcache:proxy id="proxyElementos" refId="elementoDao">
<ehcache:caching methodName="get*" cacheName="cacheElementos"/>
</ehcache:proxy>
</beans>

En este archivo se configuran los siguientes beans:
  • Un datasource sencillo a nuestra base de datos.
  • Una plantilla de acceso a JDBC de Spring.
  • Un objeto de acceso a datos para recuperar elementos de la base de datos.
  • Un bean proxy que permite incorporar cacheo en las llamadas al bean DAO.

En cuanto al los beans datasource y jdbcTemplate, no hay que hacer más que seguir las instrucciones de configuración de Spring. El bean elementDao pertenece a la clase ElementDaoImpl que implementa la interfaz ElementDao. A continuación vemos la implementación de la clase ElementDaoImpl:
package com.roldan.spring.dao;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

import com.roldan.spring.Prueba;
import com.roldan.spring.model.Elemento;

public class ElementoDaoImpl implements ElementoDao {

private static Logger logger =
Logger.getLogger(ElementoDaoImpl.class);

public static final String RECUPERAR_ELEMENTOS =
"SELECT * FROM ELEMENTO WHERE GRUPO = ?";
public static final String RECUPERAR_TODOS =
"SELECT * FROM ELEMENTO";

public List getElements(int grupo) {
List matches = jdbcTemplate.query(
RECUPERAR_ELEMENTOS,
new Object[] {Integer.valueOf(grupo)},
new RowMapper() {
public Object mapRow(ResultSet rs, int rowNum)
throws SQLException {
Elemento elemento = new Elemento();
elemento.setId(rs.getInt(1));
elemento.setDescripcion(rs.getString(2));
elemento.setGrupo(rs.getInt(3));

logger.info("Se mapea el elemento " + elemento.getId());

return elemento;
}
});
return matches;
}

public List getAllElements() {
List matches = jdbcTemplate.query(RECUPERAR_TODOS,
new Object[] {},
new RowMapper() {
public Object mapRow(ResultSet rs, int rowNum)
throws SQLException {
Elemento elemento = new Elemento();
elemento.setId(rs.getInt(1));
elemento.setDescripcion(rs.getString(2));
elemento.setGrupo(rs.getInt(3));

logger.info("Se mapea el elemento " + elemento.getId());

return elemento;
}
});

return matches;
}

JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
}

Tras esto, sólo nos queda revisar la configuración de EhCache en Spring, realizada a través del módulo spring-modules-cache. En el archivo spring-config.xml vemos que se ha definido que el archivo de configuración de EhCache es ehcache.xml:
<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
<defaultCache
maxElementsInMemory="100"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LFU"/>
<cache name="cacheElementos"
maxElementsInMemory="100"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="1"
memoryStoreEvictionPolicy="LFU"/>
</ehcache>

En este archivo se configura una cache por defecto, que es obligatoria, y una cache para las aplicación. En esta caché se pueden guardar hasta 300 elementos, y cada elemento se corresponde con una llamada a un método con unos determinados parámetros. Si se repiten llamadas al mismo método con los mismos valores de parámetros, el resultado de la llamada estará cacheado y no será necesario ir a la base de datos a recuperarlo.

Por ejemplo, si ejecutamos la siguiente clase de prueba:
package com.roldan.spring;

import java.util.List;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.roldan.spring.dao.ElementoDao;
import com.roldan.spring.model.Elemento;

public class Principal {

public static void main(String[] args) {
ApplicationContext ctx =
new ClassPathXmlApplicationContext("spring-config.xml");

ElementoDao elementoDao = (ElementoDao) ctx.getBean("proxyElementos");

List elementos = elementoDao.getElements(1);
for (Elemento elemento : elementos) {
System.out.println(elemento.getDescripcion());
}

elementos = elementoDao.getAllElements();
for (Elemento elemento : elementos) {
System.out.println(elemento.getDescripcion());
}

elementos = elementoDao.getElements(1);
for (Elemento elemento : elementos) {
System.out.println(elemento.getDescripcion());
}
}
}

Se obtienen los siguientes logs:

DEBUG - Creating JDK dynamic proxy: target source is SingletonTargetSource for target object [com.roldan.spring.dao.ElementoDaoImpl@2a6f16]
DEBUG - Finished creating instance of bean 'proxyElementos'
DEBUG - Returning cached instance of singleton bean 'proxyElementos'
DEBUG - Attempt to retrieve a cache entry using key <1130820531|30562044> and cache model
DEBUG - Retrieved cache element
DEBUG - Executing prepared SQL query
DEBUG - Executing prepared SQL statement [SELECT * FROM ELEMENTO WHERE GRUPO = ?]
DEBUG - Fetching JDBC Connection from DataSource
DEBUG - Creating new JDBC DriverManager Connection to [jdbc:mysql://localhost/pruebacache]
INFO - Se mapea el elemento 1
INFO - Se mapea el elemento 3
DEBUG - Returning JDBC Connection to DataSource
DEBUG - Attempt to store the object <[com.roldan.spring.model.Elemento@7109c4, com.roldan.spring.model.Elemento@1385660]> in the cache using key <1130820531|30562044> and model
DEBUG - Object was successfully stored in the cache
Primer elemento
Tercer elemento
DEBUG - Attempt to retrieve a cache entry using key <23191881|23191477> and cache model
DEBUG - Retrieved cache element
DEBUG - Executing prepared SQL query
DEBUG - Executing prepared SQL statement [SELECT * FROM ELEMENTO]
DEBUG - Fetching JDBC Connection from DataSource
DEBUG - Creating new JDBC DriverManager Connection to [jdbc:mysql://localhost/pruebacache]
INFO - Se mapea el elemento 1
INFO - Se mapea el elemento 2
INFO - Se mapea el elemento 3
INFO - Se mapea el elemento 4
DEBUG - Returning JDBC Connection to DataSource
DEBUG - Attempt to store the object <[com.roldan.spring.model.Elemento@4ecfdd, com.roldan.spring.model.Elemento@30d82d, com.roldan.spring.model.Elemento@c09554, com.roldan.spring.model.Elemento@18bf072]> in the cache using key <23191881|23191477> and model
DEBUG - Object was successfully stored in the cache
Primer elemento
Segundo elemento
Tercer elemento
Cuarto elemento
DEBUG - Attempt to retrieve a cache entry using key <1130820531|30562044> and cache model
DEBUG - Retrieved cache element <[com.roldan.spring.model.Elemento@7109c4, com.roldan.spring.model.Elemento@1385660]>
Primer elemento
Tercer elemento

Se pueden apreciar los siguientes pasos:
  • En la primera llamada se buscan todos los elementos que pertenecen al grupo 1. La llamada pasa por el proxy, que trata de buscar el resultado de esta llamada en la cache y, como no lo encuentra, acude a base de datos.
  • En la segunda llamada se buscan todos los elementos. Esta llamada también pasa por el proxy, que trata de buscar el resultado de esta llamada en la cache y, como no lo encuentra, acude a base de datos.
  • En la tercera llamada se buscan de nuevo todos los elementos que pertenecen al grupo 1. Al pasar por el proxy, éste reconoce que el resultado de la llamada está en la caché y que no hace falta acudir a la base de datos, ahorrándose la consulta.

Con esto, tenemos ya un ejemplo de cómo utilizar una cache en las consultas a base de datos en Spring.

Referencias:
http://www.dosideas.com/wiki/EhCache_Con_Spring

2 comentarios:

  1. Gracias Jorge, muy interesante. La verdad es que lo leeré al completo cuando tenga un rato ya que por desgracia tampoco conozco Ehcache ni he tenido oportunidad de probar ningún producto que se le asemeje. Saludos

    ResponderEliminar
  2. Muy buen Post, Felicitaciones!

    Dejo más info en:

    http://emanuelpeg.blogspot.com/2010/05/configurar-una-cache-con-spring-con.html

    Saludos!

    ResponderEliminar