Mostrando entradas con la etiqueta Bases de datos. Mostrar todas las entradas
Mostrando entradas con la etiqueta Bases de datos. Mostrar todas las entradas

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

viernes, 30 de octubre de 2009

Pruebas de base de datos con DbUnit

Cuando se desarrollan aplicaciones que acceden a una base de datos, es conveniente verificar que los datos se están manipulando de forma correcta. Para ello, es necesario hacer pruebas sobre el código que realiza esta manipulaciones de datos.

JUnit es un framework que nos ayuda a la hora de hacer pruebas en nuestra aplicación. Además, también podemos usar DbUnit, que es una extensión de JUnit que permite llevar la base de datos a un estado definido antes de cada prueba y que proporciona clases que nos ayudan a realizar pruebas sobre el contenido de una base de datos.

DbUnit permite importar y exportar el contenido de la base de datos a ficheros xml. Por ejemplo, la tabla PROFESORES se define de la siguiente forma:

Un registro en la tabla PROFESORES, se expresaría en xml como:
<profesores PROFESOR_ID="3" NOMBRE="nombre_profesor"/>

Se puede apreciar que el nombre del elemento es el nombre de la tabla y los distintos atributos son los campos de la tabla y su valor para este registro.

Mediante registros de este tipo se pueden generar distintos conjuntos de datos (datasets) en ficheros xml, que pueden servir como semillas para probar el código que accede a la base de datos en situaciones adecuadas para cada prueba.

Vamos a ver como exportar un esquema completo de la base de datos utilizando DbUnit. En primer lugar, se debe obtener una conexión a la base de datos. Se puede hacer de la siguiente forma:
Class.forName("com.mysql.jdbc.Driver");
Connection jdbcConnection =
DriverManager.getConnection(
"jdbc:mysql://localhost/bdprueba",
"user",
"pwd");
IDatabaseConnection connection =
new DatabaseConnection(jdbcConnection);

Posteriormente pasamos a exportar los datos que hay actualmente en la base de datos. Al exportar hay que tener en cuenta el orden de las tablas ya que se podría romper la integridad referencial que existe entre ellas. Para ello se crea un dataset al que se añaden las tablas de forma ordenada, que se pueden obtener a partir de un DatabaseSequenceFilter:
DatabaseSequenceFilter filter = 
new DatabaseSequenceFilter(connection);
IDataSet datasetAll =
new FilteredDataSet(
filter,
connection.createDataSet());
QueryDataSet partialDataSet =
new QueryDataSet(connection);

String[] listTableNames =
filter.getTableNames(datasetAll);
for (int i = 0; i < listTableNames.length; i++) {
final String tableName = listTableNames[i];
partialDataSet.addTable(tableName);
}

Ahora se exportaría este dataset a un archivo xml:
FlatXmlDataSet.write(partialDataSet,
new FileOutputStream("dataset.xml"));

<?xml version='1.0' encoding='UTF-8'?<
<dataset>
<academias/>
<estilos ESTILO_ID="1" NOMBRE="nombre_estilo"
DESCRIPCION="descripcion_estilo"/>
<estilos ESTILO_ID="3" NOMBRE="nombre_estilo"
DESCRIPCION="descripcion_estilo"/>
<estilos ESTILO_ID="4" NOMBRE="nombre_estilo"
DESCRIPCION="descripcion_estilo"/>
<estilos ESTILO_ID="6" NOMBRE="nombre_estilo"
DESCRIPCION="descripcion_estilo"/>
<estilos ESTILO_ID="8" NOMBRE="nombre_estilo"
DESCRIPCION="descripcion_estilo"/>
<estilos ESTILO_ID="9" NOMBRE="nombre_estilo"
DESCRIPCION="descripcion_estilo"/>
<estilos ESTILO_ID="10" NOMBRE="nombre_estilo"
DESCRIPCION="descripcion_estilo"/>
<estilos ESTILO_ID="12" NOMBRE="nombre_estilo"
DESCRIPCION="descripcion_estilo"/>
<clases/>
<academia_clase/>
<profesores PROFESOR_ID="3" NOMBRE="nombre_profesor"/>
<profesores PROFESOR_ID="6" NOMBRE="nombre_profesor"/>
<profesores PROFESOR_ID="9" NOMBRE="nombre_profesor"/>
<profesores PROFESOR_ID="12" NOMBRE="nombre_profesor"/>
<clase_profesor/>
<profesor_estilo PROFESOR_ID="3" ESTILO_ID="3"/>
<profesor_estilo PROFESOR_ID="6" ESTILO_ID="6"/>
<profesor_estilo PROFESOR_ID="9" ESTILO_ID="9"/>
<profesor_estilo PROFESOR_ID="12" ESTILO_ID="12"/>
</dataset>

Tras esto se ha obtenido un dataset que puede ser usado como semilla para una prueba.

A la hora de hacer pruebas, DbUnit proporciona la clase DatabaseTestCase, que sirve como clase base para las pruebas. Esta clase tiene algunos métodos que debemos sobreescribir. El método getConnection() proporciona la conexión a la base de datos, de forma similar a como hemos visto antes:
protected IDatabaseConnection getConnection() 
throws Exception {

Class driverClass =
Class.forName("org.gjt.mm.mysql.Driver");

Connection jdbcConnection =
DriverManager.getConnection(
"jdbc:mysql://localhost/bdprueba",
"user",
"pwd");

return new DatabaseConnection(jdbcConnection);
}

El método getDataSet() proporciona un dataset que sería la semilla para la prueba que vamos a ejecutar:

protected IDataSet getDataSet() throws Exception {
return new FlatXmlDataSet(
new FileInputStream("dataset.xml "));
}

Los métodos getSetUpOperation() y getTearDownOperation() indican lo que debe hacer cada método antes y después de cada prueba. Antes de cada prueba vamos a limpiar la base de datos y a volver a dejarla en el estado que proporciona el dataset que hemos exportado anteriormente, y después de cada prueba no haremos nada:
protected DatabaseOperation getSetUpOperation() 
throws Exception {
return DatabaseOperation.CLEAN_INSERT;
}

protected DatabaseOperation getTearDownOperation()
throws Exception {
return DatabaseOperation.NONE;
}

Tras esto, podemos escribir nuestros casos de prueba en la clase que extiende de DatabaseTestCase, sabiendo que las pruebas partirán de un estado definido.

Referencias:
http://dbunit.sourceforge.net/index.html
http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=DBUnitEI
http://onjava.com/pub/a/onjava/2004/01/21/dbunit.html
http://www.oreillynet.com/onjava/blog/2005/10/dbunit_made_easy.html