Mostrando entradas con la etiqueta JDBC. Mostrar todas las entradas
Mostrando entradas con la etiqueta JDBC. Mostrar todas las entradas

miércoles, 15 de mayo de 2013

Sustituyendo la capa de persistencia de Spring JDBC por Spring + Hibernate.

Hola a todos.

En esta entrada llevaremos la serie Introducción a Spring JDBC como solución a los problemas de uso e JDBC en proyectos productivos, que hasta la última entrega iba por la entrada 3 a un nuevo nivel sustituyendo la capa de persistencia de Spring JDBC por la combinación de Spring + Hibernate3 y anotaciones propias de Hibernate

La entrada está un poco extensa porque incluiré el código necesario para explicar los cambios.

El diagrama que representa lo que queremos hacer es el siguiente:

clip_image002

Para comenzar haremos una copia del proyecto anterior cambiándole el nombre en el mismo IDE STS de Spring.

clip_image003

Luego comenzaremos a trabajar con las interfaces. Para este caso implementaremos una interfaz genérica que nos servirá para todos los futuros DAO que tengamos que implementar de cualquier entidad del negocio.

Esta interfaz se verá así:

package org.ejemplos.blog.spring.Interfaces;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.hibernate.criterion.DetachedCriteria;

public interface BaseDao<T extends Serializable, E> {
public void deleteAll(Collection<T> instances) throws Exception;

public int bulkUpdate(String query) throws Exception;

public E save(T instance) throws Exception;

public void saveOrUpdateAll(Collection<T> instances) throws Exception;

public void saveOrUpdate(T instance) throws Exception;

public void persist(T transientInstance) throws Exception;

public void attachDirty(T instance) throws Exception;

public void attachClean(T instance) throws Exception;

public void delete(T persistentInstance) throws Exception;

public List<T> findByExample(T instance) throws Exception;

public List<T> findByQuery(String query) throws Exception;

public List<Map<String, Object>> findMapByQuery(String queryString)
throws Exception;

public List<T> findByCriteria(DetachedCriteria criteria) throws Exception;

public T merge(T detachedInstance) throws Exception;

public List<T> findAll() throws Exception;

public T findById(E id) throws Exception;
}

 


Con ayuda de la genericidad no necesitamos especificar el tipo de clase que usará la implementación de esta interface. Luego veremos como se usa y la utilidad que tiene. Solo decir que la interface está parametrizada por una clase genérica, T, y una clave primaria que a su vez es serializable. Esto nos permite que independientemente del tipo de nuestra clase podamos acceder de forma segura a sus métodos.

Estos métodos identificados en la interface nos permiten realizar casi cualquier operación sobre las entidades, es por eso que teniéndolos ya especificamos pues se nos hace más fácil la implementación.

El otro cambio que debemos realizar es en el pom.xml del proyecto para que Maven nos cargue las nuevas dependencias de spring + hibernate, quedaría de la siguiente manera:

<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>cu.casa.jorge.spring</groupId>
<artifactId>ejemplo-dao-spring-hibernate</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>

<name>ejemplo-dao-spring-hibernate</name>
<url>http://maven.apache.org</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<org.springframework.version>3.2.2.RELEASE</org.springframework.version>
<org.spring-test.version>3.2.0.RELEASE</org.spring-test.version>
<postgresql.version>9.1-901.jdbc4</postgresql.version>
<junit.version>4.11</junit.version>
<commons-dbcp.version>1.2.2</commons-dbcp.version>
<hibernate.version>3.5.0-Final</hibernate.version>
</properties>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${org.spring-test.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>${postgresql.version}</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>${commons-dbcp.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-annotations</artifactId>
<version>${hibernate.version}</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.6.1</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.12.1.GA</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.0.0.GA</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.2.0.Final</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<version>1.0.0.Final</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>

<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.4.3</version>
<executions>
<execution>
<id>default-test</id>
<phase>test</phase>
<goals>
<goal>test</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>

</build>
</project>

 


Seguimos con los cambios y entonces crearemos la interfaz para el futuro DAO que implementaremos, para seguir la idea de las entradas anteriores el DAO se usará con la entidad Capitulo así que la interface ICapitulo quedará así:

package com.test.maven.Interfaces;

import java.util.List;
import com.test.maven.Dominio.Capitulo;

public interface ICapitulo extends BaseDao<Capitulo, String> {

}

 


Como ven es un cambio significativo, ya aquí no tenemos que declarar nuevos métodos pues los extendemos de BaseDao. Es importante señalar que podríamos haber seguido con la interface inicial, pero creo que esta es mucho mejor y permitirá implementar más funcionalidades sin tener que realizarle modificaciones en un futuro.

Fíjense como cuando extendemos de BaseDao le especificamos la clase que se le debe pasar “Capitulo” y que el ID será de tipo String.

Seguimos con los cambios y nos vamos entonces a la clase entidad Capitulo, porque por la implementación que tiene del ejemplo anterior nos da un error en la interface que acabamos de crear.

Lo primero es hacer que la clase implemente la interface Serializable y luego agregarle anotaciones a la misma clase y a sus atributos. Quedando entonces de la siguiente manera:

package com.test.maven.Dominio;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity(name = "Capitulo")
@Table(name = "capitulo")
public class Capitulo implements Serializable {

private static final long serialVersionUID = 7907514315633607867L;
@Id
@Column(name = "idx", length = 100)
private String indice;

@Column(name = "titulo", length = 100, nullable = false)
private String titulo;

@Column(name = "numero_paginas", length = 100, nullable = false)
private int numero_paginas;

@Column(name = "isbn_libro", length = 100, nullable = false)
private String isbn_libro;
/*
* Aquí seguirían los métodos get y set que no cambian en nada
*/

 


Las anotaciones que hemos usado son las siguientes:


  • @Entity: indica que la clase en una entidad.
  • @Table: indica que tabla estamos mapeando en la clase.
  • @Id: indica la propiedad que actúa como identificador.
  • @Column: indica contra que columna de la tabla se mapea una propiedad.

Ahora comenzaremos con la implementación. Para ello implementaremos una clase que extiende de Serializable, de HibernateDaoSupport (un template para hibernate) y que implementa la interface BaseDao. Esta clase la reutilizo siempre entre proyectos a no ser que haya métodos que deban ser implementados de una forma particular. Por lo que la copio de un proyecto anterior. :-D

Quedaría como sigue:

package com.test.maven.DAOImpl;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import com.test.maven.Interfaces.BaseDao;

import org.hibernate.LockMode;
import org.hibernate.criterion.DetachedCriteria;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

public abstract class BaseDaoHibernate<T extends Serializable, E> extends
HibernateDaoSupport implements BaseDao<T, E> {
public void deleteAll(final Collection<T> instances) throws Exception {
try {
getHibernateTemplate().deleteAll(instances);
} catch (final Exception e) {
throw e;
}
}

public int bulkUpdate(final String query) throws Exception {
try {
return getHibernateTemplate().bulkUpdate(query);
} catch (final Exception e) {
throw e;
}
}

@SuppressWarnings("unchecked")
public E save(final T instance) throws Exception {
try {
return (E) getHibernateTemplate().save(instance);
} catch (final Exception e) {
throw e;
}
}

public void saveOrUpdateAll(final Collection<T> instances) throws Exception {
try {
getHibernateTemplate().saveOrUpdateAll(instances);
} catch (final Exception e) {
throw e;
}
}

public void saveOrUpdate(final T instance) throws Exception {
try {
getHibernateTemplate().saveOrUpdate(instance);
} catch (final Exception e) {
throw e;
}
}

public void persist(final T transientInstance) throws Exception {
try {
getHibernateTemplate().persist(transientInstance);
} catch (final Exception e) {
throw e;
}
}

public void attachDirty(final T instance) throws Exception {
try {
getHibernateTemplate().saveOrUpdate(instance);
} catch (final Exception e) {
throw e;
}
}

public void attachClean(final T instance) throws Exception {
try {
getHibernateTemplate().lock(instance, LockMode.NONE);
} catch (final Exception e) {
throw e;
}
}

public void delete(final T persistentInstance) throws Exception {
try {
getHibernateTemplate().delete(persistentInstance);
} catch (final Exception e) {
throw e;
}
}

public T merge(final T detachedInstance) throws Exception {
try {
final T result = getHibernateTemplate().merge(detachedInstance);
return result;
} catch (final Exception e) {
throw e;
}
}

@SuppressWarnings("unchecked")
public List<T> findByExample(final T instance) throws Exception {
try {
final List<T> results = getHibernateTemplate().findByExample(
instance);
return results;
} catch (final Exception e) {
throw e;
}
}

@SuppressWarnings("unchecked")
public List<T> findByQuery(final String queryString) throws Exception {
try {
final List<T> results = getHibernateTemplate().find(queryString);
return results;
} catch (final Exception e) {
throw e;
}
}

@SuppressWarnings("unchecked")
public List<Map<String, Object>> findMapByQuery(final String queryString)
throws Exception {
try {
final List<Map<String, Object>> results = getHibernateTemplate()
.find(queryString);
return results;
} catch (final Exception e) {
throw e;
}
}

@SuppressWarnings("unchecked")
public List<T> findByCriteria(final DetachedCriteria criteria)
throws Exception {
try {
return getHibernateTemplate().findByCriteria(criteria);
} catch (final Exception e) {
throw e;
}
}

public abstract List<T> findAll() throws Exception;

public abstract T findById(E id) throws Exception;
}

 


Como puede ver casi todos los métodos lo que hacen es llamar al método correspondiente de getHibernateTemplate, que es el template que estamos usando, con excepción de los 2 últimos métodos que vamos a implementar. Esto es a manera de ejemplo porque podríamos brindar la misma funcionalidad con los métodos que ya tenemos.

La implementación del DAO de Capitulo quedaría como sigue:

package com.test.maven.DAOImpl;

import java.util.List;

import com.test.maven.Dominio.Capitulo;
import com.test.maven.Interfaces.ICapitulo;

public class CapituloDAO extends BaseDaoHibernate<Capitulo , String> implements ICapitulo{

@Override
public List<Capitulo> findAll() throws Exception {

return getHibernateTemplate().loadAll(Capitulo.class);

}

@Override
public Capitulo findById(String id) throws Exception {
Capitulo capitulo = getHibernateTemplate().get(Capitulo.class, id);
return capitulo;
}

}

 


Y llegados aquí es importante decir que esta es la clase que tendremos que implementar realmente para el resto de las entidades en nuestra aplicación, ya que casi todo lo demás es reutilizable. Mediante la genericidad, especificamos que nuestro DAO es sobre la entidad Capitulo y su id es de tipo String

Como ven solo tengo que implementar los métodos que no implementé en BaseDaoHibernate.

Siguiendo con los cambios vamos a modificar el fichero de configuración de spring que usaremos para las pruebas quedando como sigue:

<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">


<context:component-scan base-package="com.test.maven" />
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="org.postgresql.Driver" />
<property name="url" value="jdbc:postgresql://localhost:5432/BDEjemplo" />
<property name="username" value="postgres" />
<property name="password" value="descarga" />
</bean>


<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan" value="com.test.maven" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>

<bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="CapituloDAO" class="com.test.maven.DAOImpl.CapituloDAO">
<property name="hibernateTemplate" ref="hibernateTemplate" />
</bean>

</beans>

 


Lo importante aquí es la creación del bean sessionFactory que será de tipo AnnotationSessionFactoryBean porque estamos usando anotaciones mediante JPA donde indicamos el bean que nos cargará la fuente de datos usando el atributo ref que nos permite referenciar otros objetos dentro del mismo contexto, los paquetes a escanear y de esa manera no tendremos que indicar las clases a buscar, y le pasamos varias propiedades al hibernate para que nos muestre las consultas que se ejecuten, le especificamos el dialecto, algo importante para que las consultas se construyan bien porque como habrán visto no hemos escrito ninguna consulta, y finalmente especificamos que la estructura de la base de datos se actualice ante cualquier cambio de nuestras clases entidad. El otro bean que creamos es el hibernateTemplate al que le inyectamos el sessionFactory anterior y por último el bean CapituloDAO al que le inyectamos el bean anterior y listo.

Recuerden que en otra entrada explicaré el mecanismo detrás de todas estas cosas nuevas, de momento basta saber que funcionan así como están. :-D

Por último nos vamos a la clase de prueba que queda como sigue:

package com.test.maven;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.Assert;

import com.test.maven.DAOImpl.CapituloDAO;
import com.test.maven.Dominio.Capitulo;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("spring-context.xml")
public class CapituloDAOTest {
@Autowired
private CapituloDAO capituloDAO;

public void setCapituloDao(CapituloDAO capituloDao) {
this.capituloDAO = capituloDao;
}

@Before
public void setUp() throws Exception {
}

@Test
public void testFindCapitulobyID() throws Exception {

Assert.isInstanceOf(Capitulo.class, capituloDAO.findById("23"));

}
@Test
public void testRetornaCapitulo() throws Exception{

Capitulo capitulo = capituloDAO.findById("23");
Assert.notNull(capitulo);
}
@Test
public void testListadoCapitulos() throws Exception{

Assert.notEmpty(capituloDAO.findAll());
}
}

 


Cuando corremos esta clase con mvn test o en el STS dando clic derecho en el proyecto Run As/ Maven test, veremos lo siguiente en la consola:

-------------------------------------------------------

T E S T S

-------------------------------------------------------

Running com.test.maven.CapituloDAOTest

may 03, 2013 6:43:50 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions

Información: Loading XML bean definitions from class path resource [com/test/maven/spring-context.xml]

may 03, 2013 6:43:50 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh

Información: Refreshing org.springframework.context.support.GenericApplicationContext@2cf0bca1: startup date [Fri May 03 18:43:50 EDT 2013]; root of context hierarchy

may 03, 2013 6:43:50 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons

Información: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@a39e3dd: defining beans [editoraDAO,libroDAO,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.annotation.internalPersistenceAnnotationProcessor,dataSource,sessionFactory,hibernateTemplate,CapituloDAO,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor]; root of factory hierarchy

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".

SLF4J: Defaulting to no-operation (NOP) logger implementation

SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

may 03, 2013 6:43:51 PM org.springframework.orm.hibernate3.LocalSessionFactoryBean buildSessionFactory

Información: Building new Hibernate SessionFactory

Hibernate: select capitulo0_.idx as idx0_0_, capitulo0_.isbn_libro as isbn2_0_0_, capitulo0_.numero_paginas as numero3_0_0_, capitulo0_.titulo as titulo0_0_ from capitulo capitulo0_ where capitulo0_.idx=?

Hibernate: select capitulo0_.idx as idx0_0_, capitulo0_.isbn_libro as isbn2_0_0_, capitulo0_.numero_paginas as numero3_0_0_, capitulo0_.titulo as titulo0_0_ from capitulo capitulo0_ where capitulo0_.idx=?

Hibernate: select this_.idx as idx0_0_, this_.isbn_libro as isbn2_0_0_, this_.numero_paginas as numero3_0_0_, this_.titulo as titulo0_0_ from capitulo this_

Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.521 sec

Results :

Tests run: 3, Failures: 0, Errors: 0, Skipped: 0

may 03, 2013 6:43:52 PM org.springframework.context.support.AbstractApplicationContext doClose

Información: Closing org.springframework.context.support.GenericApplicationContext@2cf0bca1: startup date [Fri May 03 18:43:50 EDT 2013]; root of context hierarchy

[INFO] ------------------------------------------------------------------------

[INFO] BUILD SUCCESS

[INFO] ------------------------------------------------------------------------

[INFO] Total time: 5.472s

[INFO] Finished at: Fri May 03 18:43:52 EDT 2013

[INFO] Final Memory: 8M/104M

[INFO] ------------------------------------------------------------------------

Como se ve los 3 test pasaron sin problema. Por lo que con seguridad podemos decir que nuestra implementación está correcta.

Ahora si quisiéramos seguir con este proyecto implementando los restantes DAO podríamos reutilizar casi todo el código que hemos escrito lo que incrementaría notablemente nuestra productividad y todo gracias a la combinación de Spring + Hibernate.

{ Leer Más }


viernes, 3 de mayo de 2013

Introducción a Spring JDBC como solución a los problemas de uso e JDBC en proyectos productivos. III

En la entrada “Utilidad de MAVEN para aquellos que desarrollan en JAVA.” Vimos cómo usar Maven para la gestión de las dependencias de un proyecto y su compilación. En esta entrada veremos cómo reutilizar lo que sabemos de Maven en el proyecto que hemos ido desarrollando y evolucionando en las entradas 1 y 2 de esta serie.

Los pasos a seguir son estos:

1.- Usaremos el proyecto ya creado con Maven y moveremos los paquetes del proyecto anterior para src/main/java.

clip_image001

De momento no haremos nada en la ubicación src/test/java pues es para las pruebas que se realizarán al final de esta entrada

2.- En el caso que estamos probando mantenemos la misma implementación de la clase entidad Capitulo, nada cambia aquí. En el caso de la clase interface ICapitulo y su implementación pues agregamos un nuevo método para obtener el capítulo dado su ID.

Esta es la clase interface.

clip_image002

Y esta es su implementación:

clip_image004

Vean que tenemos las consultas en variables para una mejor gestión de las mismas y que hemos agregado la implementación del método “getCapitulobyID”.

De la plantilla de JDBC usamos los métodos “query” para la consulta de selección, ”update” para la consulta de inserción y “queryforObject” para obtener un objeto.

Esta implementación depende de la clase “CapituloMapper” que es la que nos permite crear una instancia de la entidad “Capitulo” por cada tupla devuelta por la consulta. La podemos ver en la siguiente imagen.

clip_image005

La he separado porque así la puedo reutilizar en diferentes métodos.

Y bueno estos fueron los cambios en la implementación. ¿Pocos, verdad?

3.- Lo otro que haremos será cambiar la forma en que probamos que todo funciona. En la entrada 2 de esta serie veíamos como creábamos una clase Test1 para probar los métodos de nuestra implementación desde un Main. Es funcional pero no práctico cuando nuestra capa de acceso a datos crece y se complica su mantenimiento. La solución que proponemos es hacer uso de la programación guiada por pruebas a partir de los frameworks Junit y Spring-test.

Debemos crear una clase que contenga métodos que nos permitan probar automáticamente los métodos de obtener el listado de capítulos y de obtener un capitulo dado su ID. La clase que implementa esta funcionalidad es la siguiente:

clip_image006

Lo primero que hacemos es hacer uso de la anotación @RunWith para indicar que no usaremos Junit si no el framework de Spring para correr las pruebas.

Luego usamos la anotación @ContextConfiguration para indicarle donde está el fichero de configuración de Spring que es el siguiente:

clip_image008

Tenemos la variable capituloDAO anotada con @AutoWire, esto indica que la misma será inyectada automáticamente con la implementación CapituloDAO.

Luego tenemos 3 métodos anotados con @Test y estos son nuestros métodos de prueba. Cada uno hace uso de la clase Assert y diferentes métodos.

El método “isInstanceOf” se encarga de determinar si el segundo parámetro que se le pasa es una instancia del primer parámetro.

El método notNull se encarga de validar si el valor que se le pasa no es nulo.

El método notEmpty recibe una colección y nos valida que no está vacía.

Como ya estamos trabajando en un proyecto MAVEN usaremos su funcionalidad par correr las pruebas así que si fuéramos a la raíz del proyecto y ejecutáramos el comando mvn test o si desde el Eclipse le diéramos clic derecho al proyecto y luego /Run As/Maven Test veríamos un resultado como el siguiente(es solo una parte de toda la información resultante de la ejecución del comando):

clip_image010

Como ven nos dice que se ejecutaron 3 pruebas, con 0 fallas y 0 errores. O sea que las 3 fueron exitosas.

Ahora removeré de la tabla “capitulo” la tupla con ID(idx) igual a 23 y veremos qué es lo que pasa.

clip_image011

Al ejecutar de nuevo las pruebas tenemos el siguiente resultado.

clip_image013

Solo una prueba tuvo éxito, la que no necesitaba de la tupla con ID 23, en este caso el test de obtener el listado de capítulos. Los resultados dicen que ocurrieron 2 errores y que podemos ver los detalles en el directorio surefire-reports.

clip_image015

Como ven nos dice que se esperaba un resultado devuelto por la ejecución de los métodos pero que no se obtuvo el resultado esperado. Si vuelven a incluir una tupla en la tabla “capitulo” con ID igual a 23 se solucionará el problema.

Para terminar si quisiéramos crear el jar para empaquetar nuestras clases bastaría con ejecutar el comando mvn install y con eso se crearía un fichero ejemplo-de-prueba-1.0-SNAPSHOT.jar siempre que se pasaran todas las pruebas definidas con anterioridad.

El ejemplo es bastante básico pero muestra:

  1. Lo fácil que es mover un proyecto para Maven. Incluso en este caso lo hice manual pero si buscan un poco verán comandos o funcionalidades en el Eclipse para hacerlo de forma automática.
  2. La forma de implementar pruebas con spring.
  3. Automatizar la ejecución de estas pruebas con Maven.

En otras entradas iremos mejorando la forma de implementar todo esto enseñándoles como pasar de Spring JDBC a Spring+Hibernate+JPA.

Adjunto el código fuente de este proyecto.

{ Leer Más }


martes, 30 de abril de 2013

Introducción a Spring JDBC como solución a los problemas de uso e JDBC en proyectos productivos. (II)

En la primera parte de esta entrada vimos la teoría y diseño detrás del framework Spring JDBC. En esta entrada estaremos viendo cómo funciona en la práctica.

Estaremos usando la misma BD de la entrada "Uso de JDBC. Sus limitaciones."

Como IDE usaré el STS o SpringTollSuite

 

Fig.1. Imagen inicial de SpringIDE.

Lo que haremos será lo siguiente:

  1. Crearemos  un proyecto JAVA y le agregaremos las dependencias de Spring, esto lo pueden ver como se hace en esta entrada: http://www.giantflyingsaucer.com/blog/?p=1524  
  2. Crearemos los beans, uno por cada entidad de la BD, tal y como en la entrada anterior.
  3. Diseñaremos las interfaces DAO y realizaremos su implementación tal y como se expresa en la figura 1 de la entrada I. Es importante destacar que los métodos de la clase interface serán todas las operaciones que se quieran realizar sobre los DAO.
  4. En las implementaciones de las interfaces DAO estaremos usando la plantilla JdbcTemplate y veremos como esta se crea inyectándole la fuente de datos. Algo propio de Spring.
  5. Por ultimo veremos el fichero de configuración con una configuración mínima para la fuente de datos y la carga de los beans.

Empecemos.

La estructura del proyecto la pueden ver en esta imagen:


 Fig.2. Estructura del proyecto creado.

Como pueden ver creo una estructura de paquetes donde tengo los DAO que son los beans para cada entidad de la BD.

Tengo un paquete para las interfaces donde se declaran los métodos que representan las acciones sobre las tablas de la BD.

Tengo otro paquete para las implementaciones de estas interfaces y es en estas implementaciones donde usaremos la plantilla con el código nuevo a explicar, porque todo lo demás es lo mismo de siempre.

Finalmente tengo un paquete para las pruebas, como aún no hemos escrito nada sobre pruebas con JUnit pues las pruebas son muy burdas pero cumplen su objetivo.

Si se fijan también verán un fichero spring-config.xml que es donde almacenamos las configuraciones y pueden ver también algunas de las librerías que se necesitan para poder ejecutar el proyecto y que listamos a continuación. Esto cuando introduzcamos Maven pues no se gestionará automáticamente. De momento este es el listado:

commons-dbcp-1.2.2.jar

commons-logging-1.1.1.jar

commons-pool-1.5.6.jar

org.springframework.aop-3.1.0.RELEASE.jar

org.springframework.asm-3.1.0.RELEASE.jar

org.springframework.aspects-3.1.0.RELEASE.jar

org.springframework.beans-3.1.0.RELEASE.jar

org.springframework.context.support-3.1.0.RELEASE.jar

org.springframework.context-3.1.0.RELEASE.jar

org.springframework.core-3.1.0.RELEASE.jar

org.springframework.expression-3.1.0.RELEASE.jar

org.springframework.instrument.tomcat-3.1.0.RELEASE.jar

org.springframework.instrument-3.1.0.RELEASE.jar

org.springframework.jdbc-3.1.0.RELEASE.jar

org.springframework.jms-3.1.0.RELEASE.jar

org.springframework.orm-3.1.0.RELEASE.jar

org.springframework.oxm-3.1.0.RELEASE.jar

org.springframework.test-3.1.0.RELEASE.jar

org.springframework.transaction-3.1.0.RELEASE.jar

org.springframework.web.portlet-3.1.0.RELEASE.jar

org.springframework.web.servlet-3.1.0.RELEASE.jar

org.springframework.web.struts-3.1.0.RELEASE.jar

org.springframework.web-3.1.0.RELEASE.jar

postgresql-9.1-901.jdbc4.jar

Si se fijan  he puesto todas las librerías que vienen con la distribución de spring, aunque no las usemos todas pero en aras de ahorrar tiempo, también agregué la del driver de postgresql y algunas commons para manejar las conexiones.

Bien una vez creado el proyecto siguiendo el enlace que les compartí y añadidas estas clases a la carpeta lib, que tienen que crear, y puestas en el classpath les muestro la estructura de una clase que mapea a una entidad de la BD.



Fig.3. Clase que representa al bean para la entidad  capítulo.

Como pueden ver es muy básica, ya la habíamos visto en otra entrada, así que no comentaremos nada de aquí.

La declaración de su interface le sigue:

 

Fig.4. Interface para el DAO.

También muy básica, solo pondremos 2  métodos. Uno para listar todos los capítulos y otro para añadir un capitulo.

Su implementación es la que nos interesa, veamos cómo se hace.

 

Fig.5. Implementación del DAO que incluye el uso de la plantilla de spring para JDBC.

Como pueden ver comenzamos anotando, de momento no se preocupen por entender esto ya en otra entrada lo explicaré con detalle, la clase CapituloDAO con @Repository y diciendo que implementa la interface ICapitulo.

Luego declaramos las consultas en variables y una última variable de tipo la plantilla que usaremos.

Implementamos un método que recibe un Datasource y con este objeto crea una plantilla y se la asigna a la variable antes creada. Este método también está anotado con @Autowired.

Luego vienen los métodos para listar los capítulos, vean que poco de código hay que escribir. Es solo decirle al template que nos ejecute la consulta que tenemos en CAPITULO_SELECT y pasarle un objeto de tipo RowMapper y es aquí donde pasa la magia porque por cada tupla de la BD que represente un capitulo se llamará al método mapRow que se encarga de crear un objeto de tipo Capitulo y asignarle los valores que vienen en la tupla y devolver el objeto de tipo Capitulo ya creado. Luego de procesadas todas las tuplas pues se devuelve el listado de los capítulos.

En el caso de añadir un libro pues es más fácil aun.  Se llama al método update de la plantilla, se le pasa la consulta que se definió en CAPITULO_INSERT y se le pasa un arreglo de objetos que se corresponden con los valores de los atributos de la clase pasada al método y que se desea insertar en la BD.

Aquellos que quieran mejorar este paso de los parámetros a la consulta pueden usar la plantilla NamedParameterJdbcTemplate. Con esta plantilla le ponemos nombre a los parámetros y así no importa el orden en que los pasemos, algo muy importante por si se hacen cambios en la estructura de la BD y se altera el orden de los campos en la BD o en la consulta.

Ahora veremos cómo es que se especifica el fichero de configuración para spring, porque si recuerdan el código de la entrada de JDBC aún no he puedo cómo crear la conexión a la BD. Ahora lo veremos.


Fig.6. Contenido del fichero de configuración de Spring.

Como pueden ver solo creamos un bean con id=dataSource que es el que le inyectamos al método que se encarga de  crear el template, de esto se encarga Spring JDBC y creamos otro bean para el manejo de las transacciones para el método de insertar. No hay que crear más beans aquí porque estos son creados al vuelo por spring gracias a las anotaciones que les comentaba anteriormente y que les explicaré cómo funcionan en otra entrada.

Para comprobar que esto de verdad funciona pues haremos lo siguiente:

  1. Crearemos  una interface del objeto de negocio que debe interactuar con el DAO.
  2. Implementaremos esta interface.
  3. Usaremos una clase para cargar la configuración de spring y crear un objeto de negocio para acceder a la capa de persistencia.

Esta es la interface:


Fig.7. Interface de la clase del negocio que usará la capa de persistencia definida.

Y esta es su implementación:

 

Fig.8. Implementación de la clase de negocio.

Vean que sencillo es el código para interactuar con la BD. De ahí no sabemos cuál es el motor de persistencia que se está usando ni las complejidades del mismo.

Noten las anotaciones @Service, @Autowired y @Transactional.

La clase que consumirá esta implementación es la siguiente:

 

Fig.9. Clase de prueba para comprobar el funcionamiento de la capa de persistencia.

Y cuando la ejecutamos vemos lo siguiente:


Fig.10. Resultado en la consola.

Como pueden ver si listan todos los títulos existentes en la BD como pueden ver aquí:

 

Fig.11. datos en la BD antes de ejecutar las consultas a la BD.

Y se inserta un nuevo libro en la BD que podemos verlo a continuación:

 

Fig.12. Imagen que muestra el libro insertado luego de ejecutar el código de prueba.

Y bueno eso es todo. Quedan algunas cosas pendientes como:

  • Depurar un poco la implementación para mejorar el código. Es de ejemplo pero  siempre se puede mejorar.
  • Capturar información para hacer logging.
  • Revisar el trabajo con spring y con las anotaciones.
  • Ver el uso de otras plantillas para JDBC.
  • Ver como probar con JUnit.
  • Ver cómo se pueden gestionar las dependencias para no tener que agregarlas manualmente.

Son cosas que poco a poco iremos tocando en el blog porque son básicas para cualquier desarrollo serio.

Espero les haya resultado de utilidad y esperamos sus comentarios.

{ Leer Más }


lunes, 29 de abril de 2013

Introducción a Spring JDBC como solución a los problemas de uso e JDBC en proyectos productivos. (I)

En una entrada anterior les había comentado sobre JDBC y sus limitaciones que más que limitaciones eran problemas. Estos problemas se resumían en:

  • Se debe escribir demasiado código para cosas simples como operaciones CRUD.
  • Mucho del código a escribir es repetitivo lo que puede llevar a errores o inconsistencias.
  • Se debe manejar la creación de las conexiones, la preparación de las consultas su ejecución y el cierre de las conexiones.
  • Se debe manejar las excepciones, lo cual no vimos en la entrada, pero siempre es algo que hay que hacer con JDBC.

Pues bueno en esta entrada les traigo una solución para aquellos que quieren seguir usando JDBC, bien porque les gusta y no quieren aprender un nuevo framework o bien porque su proyecto es pequeño o mediano y no ven la necesidad de usar un cañón para matar un mosquito la solución es usar el framework "Spring JDBC".

Para comenzar en este framework quisiera comentarles primero sobre los DAO. Este es un acrónimo de  "Data Access Object" un patrón de diseño muy común que se usa para diseñar como los objetos de negocio de una aplicación deben usar los objetos encargados del acceso a los datos.

Lo pueden ver en esta imagen:


Fig.1. Esquema que ilustra el patrón DAO.

Como pueden ver los objetos del negocio acceden a los DAO a través de sus interfaces, de esta manera no dependen de una implementación en específico lo que hace que esta implementación se pueda cambiar fácilmente. También facilita las pruebas que se pueden realizar sobre esta implementación, ya que se pueden crear implementaciones falsas menos complejas que las implementaciones reales con fines de prueba.

Esta manera de separar la capa de acceso a datos y su implementación del resto del código de la aplicación es usada por Spring para entre otras muchas cosas manejar las excepciones. El argumento se basa en que casi todas las excepciones generadas por JDBC no se pueden recuperar por lo que Spring ha definido sus propias excepciones que se mapean de la siguiente manera:


Fig.2. Excepciones de JDBC y de Spring JDBC.

Lo importante de este mapeo es que no es específico para JDBC, si no para cualquier framework de persistencia como puede ser hibernate, lo que contribuye al uso del patrón DAO.

Hay que tener en cuenta que estas excepciones no necesitan de los bloques catch tan comunes ya que las excepciones no son chequeables. No necesitan serlo ya que no se puede hacer prácticamente nada con ellas.

Lo otro que hace Spring JDBC es crear una especie de plantillas para agrupar todas aquellas cosas que son repetitivas dentro del código para acceder a los datos. Recuerdan los problemas listados al inicio, pues estas plantillas resuelven si no todos la mayoría. Vean cómo funcionan.


Fig.3. Esquema de funcionamiento de las plantillas en Spring para el acceso a los datos.

La plantilla creada agrupa todas aquellas acciones que son repetitivas y nos las oculta, y solo nos deja encargarnos de lo que de verdad es útil. Ejecutar las consultas y retornar la información generada por estas consultas.

Spring ha definido numerosas plantillas para diferentes frameworks de persistencia como pueden ver en esta imagen.


Fig.4. Listado de las clases  y plantillas que soporta Spring para el acceso a datos.

Llegado a este punto vemos que es necesario crear un mecanismo de callback para poder usar la plantilla. O sea que nuestros DAO deben de tener una propiedad de tipo la plantilla que usaremos y debe definir los métodos get/set para manejar esta propiedad. Aunque esto no es incómodo de hacer si se repite bastante cuando tenemos muchos DAO y para cada uno de ellos puede debemos de incorporar la misma plantilla.

Spring también nos libera de esto a través del uso de métodos dentro de las plantillas que nos ayudan a no tener que implementar estos callback. De esta manera Spring define clases de soporte de las cuales podemos extender con nuestras propias implementaciones. Vean su diseño:


Fig.5. Esquema de diseño de los DAO de soporte en Spring.

De esta manera podemos tener una clase DAO support de la cual extendemos para crear nuestros DAO y de esta manera pues el manejo de la plantilla se hace de forma transparente para los desarrolladores.

Aquí les dejo el listado de DAO de soporte que da Spring y el acceso que proveen.


Fig.6. Listado de las clases de soporte DAO y el acceso que brindan.

Para terminar esta entrada solo comentarles que con el framework Spring JDBC y sus componentes que ya he identificado el código a escribir se reduce de forma significativa y lo podremos ver en la próxima entrada que será la parte práctica de esta introducción.

{ Leer Más }


viernes, 19 de abril de 2013

Uso de JDBC. Sus limitaciones.

En esta entrada veremos de forma práctica algunas de las cosas que comentábamos en una de las entradas iniciales del blog relacionadas con el acceso a dato.

Estaremos haciendo lo siguiente:

  • Usaremos una BD en postgresql para almacenar información y consumirla.
  • Usaremos un driver de postgresql para conectarnos a esta BD desde JAVA.
  • Usaremos JDBC para conectarnos a la BD y ejecutar diferentes consultas contra la BD.

Empecemos:

Paso 1:


Lo primero es tener creada una BD en postgresql, yo uso pgadmin con editor. Estoy usando una versión no tan nueva de postgresql pero ustedes pueden usar la versión 9.x sin problema.

Mi BD es esta:


Y mis tablas son las siguientes:





Como ven un libro tiene una clave foránea de la editora que publica el libro y un capitulo tiene una clave foránea del libro al que pertenece. Una editora puede tener muchos libros y un libro puede tener muchos capítulos.

Paso 2: vamos a irnos al Eclipse, uso la versión JUNO, y comenzar a tirar el código para conectarnos a la BD y poder ejecutar consultas.

En este caso creamos un proyecto java bien básico y ejecutaremos en una consola nuestras clases.

Primero crearemos una clase para conectarnos a la BD. Como esta entrada es más educativa  que productiva nos saltaremos algunas consideraciones a tener en cuenta si esto fuera parte de un proyecto. La clase de conexión es la siguiente:


Esta clase será llamada desde otras clases y al método conectar cada vez que necesitemos de un objeto de tipo Connection para acceder a la BD, ya veremos cómo.

Veamos ahora las clases  POJO que hemos creado. Entiendan por POJO = Plain Old Java Objects

Paso 3: creamos las clases entidades que se mapearan contra nuestras tablas de la BD.

La primera clase es para la entidad Editora.


La segunda clase es para la entidad libro con sus setters y getter para acceder a los atributos privados, oculto estos métodos para poder ver toda la clase:


Y por último la clase capítulo, igual a las demás:


Paso 4: creamos algunas consultas simples.

Comencemos entonces viendo cómo usar el objeto de conexión y ejecutar consultas simples sobre la BD. Lo que veremos ahora es un método que obtiene el objeto conexión y con el prepara una consulta para obtener todos los libros de una editora, codificamos el código de la editora pero bien lo pueden pasar por parámetro al método.


En este caso los comentarios van dentro y el método es static para poder llamarlo sin instanciar a la clase que lo contiene, en este caso desde el Main de la clase.

Aquí pueden ver la ejecución de este método en el eclipse desde la consola.


Ahora veremos otro ejemplo donde actualizamos el nombre de un libro. Primero veamos los datos de los libros en la BD.


Luego de ejecutar este método:


Veremos lo siguiente:


Como ven en la segunda impresión ya el título "más allá de las estrellas" no aparece y en su lugar tenemos a "por quién doblan las campanas".


Paso 5: comenzamos el trabajo de hacer un ORM, algo que nos mapee de nuestras tablas de la BD a nuestras clases entidades del negocio.

Para finalizar esta entrada veremos cómo podemos a partir del trabajo con JDBC crear las entidades con los datos obtenidos de la BD.



Una vez creados estos objetos de las entidades los podemos usar para obtener la información de ellos y no de las consultas directamente de la BD. ¿Cuál es el problema con este enfoque?

El problema es que esto es el principio de cómo funciona un ORM que era algo de lo que hablábamos en una de las primeras entradas. Ver como mapear las tablas de la BD con las entidades del negocio y es algo de lo que hemos hecho aquí, pero aún nos queda mucho código por escribir y repetir y además es un código muy atado al diseño de la BD, debemos iterar manualmente sobre muchos resultados y es costoso en términos de rendimiento la preparación de las consultas. Es por eso que en proyectos productivos rara vez se usa JDBC y si se recurre a Hibernate como el mejor ORM para el trabajo con BD relacionales. Ya lo estaremos viendo en acción en posteriores entradas.

{ Leer Más }


martes, 9 de abril de 2013

Introducción al acceso a las bases de datos en JAVA. Patrones y buenas prácticas.

La mayoría de los proyectos actuales necesitan guardar o consultar datos de una base de datos relacional, de esta manera se puede persistir la información de sus usuarios y del negocio que implementan pudiendo acceder a ella en cualquier momento.

Para conectarse a las BD las aplicaciones utilizan por lo general drivers, que no son más que componente que permiten realizar una conexión amigable con la base de datos de un tipo específico. Algunos de estos tipos pueden ser MSSQL, MySQL, Oracle, PostgreSQL, etc.

Otro elemento importante a tener en cuenta es que para realizar operaciones sobre los datos se necesita de un lenguaje de consultas que permita a los desarrolladores trabajar con las bases de datos, y en este punto contamos con SQL.

Aquellos desarrolladores con poca formación o que desconozcan los patrones de diseño existentes puede ser que comentan el error de mezclar el código de la aplicación que desarrolla con el código del acceso a los datos y esto puede traer muchos problemas a corto, mediano y largo plazo. Es por esta razón que se han propuesto arquitecturas y patrones de diseño que especifican cual debe ser la forma de diseñar una aplicación y como una aplicación debe acceder a sus datos. Esto se muestra en las siguientes dos imágenes a manera de ejemplo.

Fig.1 Elementos que componen una arquitectura Modelo Vista Controlador o MVC.

En esta propuesta de arquitectura los elementos que son modelos tienen la responsabilidad de gestionar el acceso a los datos, por lo que el resto de los componentes que son vistas o controladores no tienen que preocuparse de este asunto.


Fig.2 elementos que componen una arquitectura en 3 Capas.

En este caso  la capa inferior se encarga de contener los componentes de acceso  a los datos y sistemas externos por lo que las capas superiores se abstraen de estas responsabilidades.

Como pueden ver en las imágenes  en cada propuesta se establece una separación lógica entre el código de la aplicación y el código del acceso a datos. Esto  hace posible que:
  1. Se pueda cambiar el tipo de base de datos. Ejemplo pasar de MySQL a PosgreSQL con poco cambio sin afectar el código de la aplicación.
  2. Se pueda realizar modificaciones en la estructura o diseño base de datos llevando al mínimo los cambios en la aplicación.
Otro elemento a considerar es cómo se puede combinar la programación Orientada a Objetos donde todo son clases, objetos y componentes con el acceso a datos donde todo son consultas estructuradas, tablas y tuplas de datos. En la búsqueda de una solución a este asunto se llegó a un patrón de diseño conocido como “Mapeo Objeto Relacional” u ORM por sus siglas en inglés que establece una relación entre las tablas de una base de datos y las clases entidades de una aplicación. Para simplificar este patrón diremos que la solución que propone es tener una clase entidad por cada tabla de la base de datos a la que queramos acceder. Esta clase y la tabla se consideran isomorfos o sea, tienen la misma estructura, por cada columna de la tabla se tendrá un atributo en la clase. Además La clase contendrá todo el código encargado de manejar las consultas SQL de forma que el resto de la aplicación no tenga que tratar con consultas SQL cuando se quiera manejar los datos, este código estará dentro de los métodos de la clase que se mapearán contra las operaciones que se deseen realizar sobre la base de datos.
JAVA como lenguaje primero hizo uso de JDBC para el acceso a las BD. Con esta tecnología se puede:
  1. Establecer una conexión con la BD, especificando un driver y los parámetros para autenticarse en la BD.
  2. Usar la conexión establecida para realizar operaciones sobre la BD (seleccionar datos, insertar, modificar, eliminar, etc).
  3. Procesar los resultados devueltos por las operaciones anteriores.
Esta tecnología tiene sus limitaciones y ha sido sustituida en proyectos de desarrollo de aplicaciones empresariales que usan JAVA por una implementación del patrón ORM que se ha constituido el framework por defecto cuando se va a desarrollar una aplicación JAVA con acceso a bases de datos relacionales. Estoy hablando de Hibernate. Si a eso le sumamos el uso del estándar JPA con anotaciones ya se completa la solución para el acceso a bases de datos relacionales.

Fig. 3 Ejemplos de frameworks ORM que usan JDBC.

Este es solo una entrada introductoria a otras que tratarán temas como:
  • Uso de Maven para la gestión de dependencias en proyectos JAVA.
  • Acceso a bases de datos relacionales con JDBC, Hibernate, JPA, a través de servicios RESTful, etc.
  • Uso del Spring Framework, y todos sus componentes, en el desarrollo de aplicaciones JAVA.
  • Uso de ZKoss y Vaadin para el desarrollo de la capa de presentación en aplicaciones empresariales.
{ Leer Más }


IconIconIcon