Persistencia con Spring

0
24890

Persistencia con Spring

En el siquiente tutorial vamos a ver algunas de las aportaciones que nos ofrece Spring para mejorar la capa de persistencia de nuestras aplicaciones.

Se presupone que el lector ya posee conocimientos de JUnit, Hibernate, Maven y Spring.

Indice de contenido:

  1. Introducción.
  2. ¿Qué proporciona Spring a la capa de persistencia?.
  3. Andando se hace el camino, un ejemplo:

    1. Entorno.
    2. Estructura del proyecto de ejemplo.
    3. Script de creación de la tabla de ejemplo sobre MySQL.
    4. Maven 2: Archivo de configuración.
    5. POJO.
    6. DAO usando JDBC.
    7. DAO usando Hibernate 3 (con y sin anotaciones).
    8. Spring 2: Archivo de configuración.

      1. Origenes de datos.
      2. JDBC.
      3. Hibernate 3 sin anotaciones.

        1. Archivo de mapeo de hibernate 3 para los productos (Product.hbm.xml)
      4. Hibernate 3 con anotaciones.
    9. Probando la aplicación a través de tests con JUnit 4
  4. Referencias
  5. Conclusiones

Introducción

Bien es sabido que el trabajo con bases de datos suele ser repetitivo: configurar origenes de datos, obtener conexiones, ejecutar consultas, analizar resultados, gestionar posibles errores y liberar recursos.
Además cuando estos recursos no son liberados correctamente (programadores con poca experiencia, proyectos que van mal de tiempo, despistes, etc.) suele ser fuente de graves problemas, teniendo que andar usando herramientas para detectar en que puntos de la aplicación se han dejado abiertos.

Cuando se produce una excepción de acceso a datos suele generarse una SQLException, excepción genérica donde las haya y dificil de interpretar » No existe una
jerarquía definida de excepciones que distingan los distintos errores que pudieran producirse
.

Por si fuera poco, SQLException es de obligada captura, por lo que tenemos que tener muchos try catch por el código, cuando normalmente con este tipo
de excepciones los programadores poco pueden hacer más que normalmente mostrar un mensaje de error del tipo «… inténtelo más tarde …»

¿Qué proporciona Spring a la capa de persistencia?.

  • Una serie de plantillas para trabajar con JDBC y los ORM más extendidos (Hibernate, iBatis, etc.) y que nos ahorran las tareas repetitivas
    que hemos comentado en la introducción, de manera que sólo nos debemos preocupar de realizar las tareas concretas de nuestro negocio ahorrándonos
    realizar el resto de operaciones (apertura de conexiones, liberación de recursos, etc.) evitándonos los posibles errores que pudieramos cometer, como por ejemplo, liberar recursos.
  • Una serie de DAOs (que tienen plantillas pre-asociadas) de las que pueden heredar nuestros DAOs.
  • Nos proporciona una rica jerarquía de clases de error en las operaciones de acceso a datos, es decir, adiós a la genérica SQLException.

    http://static.springframework.org/spring/docs/2.5.x/reference/dao.html#dao-exceptions.

    Además, estas excepciones no son de obligado tratamiento, por lo que nos evita tener que estar capturandolas cuando no podemos hacer nada para solventar el problema y permitiéndonos capturar las que nos interese de un modo concreto y no de forma obligada y genérica.

  • Fácil integración con los ORM más extendidos, incluso evitándonos tener que casar nuestro código a implementaciones concretas.
  • Potente y completo soporte de transacciones: Definición declarativa incluso con anotaciones del alcance y tipo de transacción.

    Declaración de transacciones a través de aspectos (AOP).

    (La transaciones no están cubiertas en este tutorial)

Andando se hace el camino, un ejemplo:

A continuación vamos a ver un completo ejemplo autocomentado del uso de Spring en la capa de persistencia de nuestra aplicación.

Entorno

El siguiente ejemplo está contruido en el siguiente entorno:

  • HP Pavilion.
  • Windows Vista Home Premium.
  • Eclipse Ganymede.
  • Java 6.
  • Maven 2.
  • Plugin Maven 4QE para Eclipse.
  • Un café y buena música.

Estructura del proyecto de ejemplo:

Una de las muchas ventajas de Maven es que estandariza la estructura de los proyectos, es decir, cualquier persona con conocimientos de Maven tendría facilidad de comprender como se estructura y dónde está cada cosa dentro del proyecto.

Vamos a comentar sólamente algunas partes que no forman parte de la estructura estándar, sino que es dependiente de esta aplicación concreta:

  1. /src/main/resources/hibernate/mappings/Product.hbm.xml:

    Archivo de mapeo de Hibernate 3 de la clase Product (ejemplo sin anotaciones).

  2. /src/main/resources/jdbc.properties:

    Propiedades de configuración de persistencia: usuario, contraseña, etc.

  3. /src/main/resources/applicationContext.xml:

    Archivo de configuración padre de Spring 2 (importará otros archivos de configuración).

  4. /src/main/resources/appContextDataSource.xml:

    Archivo de configuración de Spring 2 en donde definiremos los orígenes de datos de nuestra aplicación.

  5. /src/main/resources/appContextJDBC.xml:

    Archivo de configuración de Spring 2 en donde definiremos lo necesario para el ejemplo de acceso a través de JDBC.

  6. /src/main/resources/appContextHibernate3.xml:

    Archivo de configuración de Spring 2 en donde definiremos lo necesario para el ejemplo de acceso a través de Hibernate 3 sin anotaciones.

  7. /src/main/resources/appContextHibernate3Anotaciones.xml:

    Archivo de configuración de Spring 2 en donde definiremos lo necesario para el ejemplo de acceso a través de Hibernate 3 con anotaciones.

Archivo de configuración de Maven: pom.xml:

A continuación exponemos el archivo de configuración de Maven, se presupone que el lector ya tiene nociones de Maven.



  4.0.0
  com.autentia.tutoriales
  spring_dao_jdbc
  jar
  1.0-SNAPSHOT
  spring_dao_jdbc
  
Home
maven-compiler-plugin 1.5 1.5 UTF-8 mysql mysql-connector-java 5.1.6 commons-dbcp commons-dbcp 1.2.2 org.springframework spring-hibernate3 2.0.6 org.hibernate hibernate-annotations 3.3.1.GA org.springframework spring 2.5.6 org.springframework spring-core 2.5.6 junit junit 4.1 test

Script de creación de la tabla de ejemplo sobre MySQL:

Si no lo has borrado, MySQL tiene un esquema llamado test, vamos a crear un tabla en dicho esquema para este ejemplo.

USE test;

CREATE TABLE IF NOT EXISTS `productos` (
  `codigo` varchar(32) NOT NULL,
  `descripcion` varchar(255) default NULL,
  PRIMARY KEY  (`codigo`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Producto:

La siguiente clase representa un producto que el usuario puede comprar a través de la aplicación de comercio electrónico.

Para simplificar el código expongo sólo lo extrictamente necesario con fines de legibilidad.

Las anotaciones sólo serían necesarias para Hibernate 3 con anotaciones.

package com.autentia.tutoriales.spring.daojdbc.entity;

/**
 * Representa un producto comercial.
 * @author Carlos García. Autentia.
 */
@javax.persistence.Entity
@javax.persistence.Table(name="productos", schema="test")
public class Product  {
  /**
   * Código del producto
   */
  @javax.persistence.Id
  private String  codigo;

  /**
   * Descripción del producto
   */
  private String  descripcion;

  public String getCodigo() {
    return codigo;
  }
  public void setCodigo(String codigo) {
    this.codigo = codigo;
  }
  public String getDescripcion() {
    return descripcion;
  }
  public void setDescripcion(String descripcion) {
    this.descripcion = descripcion;
  }
}

DAO usando JDBC.

package com.autentia.tutoriales.spring.daojdbc.managers;

import java.util.List;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import com.autentia.tutoriales.spring.daojdbc.entity.Product;

/**
 * Ejemplo del uso de plantilla JDBC.
 * Observe que no hay establecimientos/liberacion de recursos y tampoco gestión de excepciones....
 * 
 *  Nota: Suponemos que la tabla existe:
 *      create table if not exists productos (codigo varchar(32) primary key, descripcion varchar(255))
 * 
 * @author Carlos García. Autentia.
 * @see http://www.mobiletest.es
 */
public class JdbcProductoDao {

  private JdbcTemplate jdbcTemplate;

  /**
   * Plantilla injectada por Spring
   */
  public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
    this.jdbcTemplate = jdbcTemplate;
  }

  /**
   * Recupera un producto por su código
   * @param codigo Código del producto a recuperar
   * @return El producto buscado
   */
  public Product get(String codigo){
    String[]   params     = new String[]{codigo};
    Product    prod     = null;

    jdbcTemplate.setMaxRows(1);

    prod = (Product) jdbcTemplate.queryForObject("select codigo, descripcion from productos where codigo=?", params, new RowMapper(){
      public Object mapRow(java.sql.ResultSet rs, int rowNum) throws java.sql.SQLException {
        Product p = new Product();
        p.setCodigo(rs.getString(1));
        p.setDescripcion(rs.getString(2));
        return p;
      }
    }); 

    return prod;
  }

  /**
   * Eliminar un producto
   */
  public void delete(Product p){
    String[]   params     = new String[]{p.getCodigo()};
    jdbcTemplate.update("delete from productos where codigo=?", params);
  }

  /**
   * Añade un nuevo producto
   * Se supone que el producto no existe.
   */
  public void insert(Product p){
    String[]   params     = new String[]{p.getCodigo(), p.getDescripcion()};
    jdbcTemplate.update("insert into productos (codigo, descripcion) values (?, ?)", params);
  }

  /**
   * Devuelve todos los productos
   */
  public List getAll(){
    List productos  = null;
    productos = jdbcTemplate.query("select codigo, descripcion FROM productos", new RowMapper(){
      public Product mapRow(java.sql.ResultSet rs, int rowNum) throws java.sql.SQLException {
        Product p = new Product();
        p.setCodigo(rs.getString(1));
        p.setDescripcion(rs.getString(2));
        return p;
      }
    }); 
    return productos;
  }
}

DAO usando Hibernate 3 (con y sin anotaciones).

package com.autentia.tutoriales.spring.daojdbc.managers;

import java.util.List;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.orm.hibernate3.HibernateTemplate;
import com.autentia.tutoriales.spring.daojdbc.entity.Product;

/**
 * Ejemplo del uso de la platilla de Hibernate
 * Observe que no hay establecimientos/liberación de recursos y tampoco gestión de excepciones....
 * 
 *  Nota: Suponemos que la tabla existe:
 *      create table if not exists productos (codigo varchar(32) primary key, descripcion varchar(255))
 * 
 * @author Carlos García. Autentia.
 * @see http://www.mobiletest.es
 */
public class HibernateProductoDao  {
  private HibernateTemplate template;

  /**
   * Plantilla inyectada por Spring
   */
  public void setTemplate(HibernateTemplate template) {
    this.template = template;
  }

  /**
   * Recupera un producto por su código
   * @param codigo Código del producto a recuperar
   * @return El producto buscado
   */
  public Product get(String codigo){
    Product       producto  = (Product) template.load(Product.class, codigo); 
    return producto;
  }

  /**
   * Eliminar un producto
   */
  public void delete(Product p){
    template.delete(p);
  }

  /**
   * Añade un nuevo producto
   */
  public void insert(Product p){
    template.saveOrUpdate(p);
  }

  /**
   * Devuelve todos los productos
   */
  public List getAll(){
    java.util.List productos  = null;
    productos = template.find("from productos", new RowMapper(){
      public Product mapRow(java.sql.ResultSet rs, int rowNum) throws java.sql.SQLException {
        Product p = new Product();
        p.setCodigo(rs.getString(1));
        p.setDescripcion(rs.getString(2));
        return p;
      }
    }); 
    return productos;
  }
}

Archivo de configuración de Spring 2 (applicationContext.xml):



    

  

Propiedades de la aplicación (jdbc.properties):

datasource.jdbcDriver=com.mysql.jdbc.Driver
datasource.url=jdbc:mysql://localhost:3306/test
datasource.username=invitado
datasource.password=invitado
hibernate.dialect=org.hibernate.dialect.MySQLDialect
# datasource.jndiName=/java:comp/env/jdbc/xxxxxxxxxx

Definición de DataSources (appContextDataSources.xml):



  

  

   

    

    

  

Para el ejemplo de JDBC (appContextJDBC.xml):



   

   

   

Archivo de mapeo de hibernate 3 para los productos (Product.hbm.xml)

Nota: En caso de Hibernate 3 con anotaciones no haría falta.



Para el ejemplo de Hibernate 3 sin anotaciones (appContextHibernate3.xml):



   

   

        hibernate/mappings/Product.hbm.xml

        ${hibernate.dialect}

   

   

Para el ejemplo de Hibernate 3 con anotaciones (appContextHibernate3Anotaciones.xml):



   

   

        com.autentia.tutoriales.spring.daojdbc.entity.Product            

        ${hibernate.dialect}

   

   

Probando la aplicación a través de tests con JUnit 4

package com.autentia.tutoriales.spring.daojdbc;

import org.junit.Assert;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.autentia.tutoriales.spring.daojdbc.entity.Product;
import com.autentia.tutoriales.spring.daojdbc.managers.HibernateProductoDao;
import com.autentia.tutoriales.spring.daojdbc.managers.JdbcProductoDao;

/**
 * JUnit 4. Tests funcionales a realizar
 * Es sólo a nivel didactico, no se puede considerar un tests reales
 */
public class ProductosTest  {

  private ApplicationContext  factory;  

  /** 
   * Inicializamos el contexto de Spring 
   */  
  @org.junit.Before  
  public void initTests(){  
    this.factory  = new ClassPathXmlApplicationContext("applicationContext.xml");
  }  

  /**
   * El siguiente test verifica la integración JDBC y Spring 
   */
  @org.junit.Test
  public void testProductJDBC() {
    try {
      JdbcProductoDao productoDao = (JdbcProductoDao) factory.getBean("productoDao");

      Product p = getRandomProduct("jdbc");

      // Insertamos un nuevo producto
      productoDao.insert(p);

      // Recuperamos el producto añadido
      p = productoDao.get(p.getCodigo());

      Assert.assertNotNull(p);
    } catch (org.springframework.dao.DataAccessException ex){
      Assert.fail();
    }
  }

  /**
   * El siguiente test verifica la integración Hibernate 3 SIN Anotaciones y Spring 
   */
  @org.junit.Test
  public void testProductHibernate3() {
    try {
      HibernateProductoDao productoDao = (HibernateProductoDao) factory.getBean("productoDaoNoAnotado");

      Product p = getRandomProduct("hib");

      // Insertamos un nuevo producto
      productoDao.insert(p);

      // Recuperamos el producto añadido
      p = productoDao.get(p.getCodigo());

      Assert.assertNotNull(p);
    } catch (org.springframework.dao.DataAccessException ex){
      Assert.fail();
    }
  }

  /**
   * El siguiente test verifica la integración Hibernate 3 CON Anotaciones y Spring 
   */
  @org.junit.Test
  public void testProductHibernate3Annotated() {
    try {
      HibernateProductoDao productoDao = (HibernateProductoDao) factory.getBean("productoDaoAnotado");

      Product p = getRandomProduct("hibann");

      // Insertamos un nuevo producto
      productoDao.insert(p);

      // Recuperamos el producto añadido
      p = productoDao.get(p.getCodigo());

      Assert.assertNotNull(p);
    } catch (org.springframework.dao.DataAccessException ex){
      Assert.fail();
    }
  }

  /**
   * El siguiente test será correcto si se lanza la excepción: org.springframework.dao.DataIntegrityViolationException
   * 
   * OJO, Observe que en este tutorial no se trata la transaccionalidad, por lo que se añadiria sólo el primer producto.
   * 
   * No se pueden tener dos productos con el mismo código
   */
  @org.junit.Test(expected=org.springframework.dao.DataIntegrityViolationException.class)
  public void testStringDataException() {
    JdbcProductoDao productoDao = (JdbcProductoDao) factory.getBean("productoDao");

    Product p = getRandomProduct("jdbc");

    // Insertamos un nuevo producto
    productoDao.insert(p);
    productoDao.insert(p);
  }

  /**
   * @return Devuelve un producto de ejemplo (dummy) con el que trabajar
   */
  private Product getRandomProduct(String pre){
    String    codigo    = pre + String.valueOf(System.currentTimeMillis());
    String    descripcion = "Descripcion " + codigo;

    Product p = new Product();
    p.setCodigo(codigo);
    p.setDescripcion(descripcion);

    return p;
  }
}

Ejecutando los tests con Maven: mvn test

Como vemos se han ejecutado correctamente los cuatro tests.

Ahora vemos que ha sucedido en nuestro modelo de datos:

Referencias

Conclusiones

Bueno, como veis Spring no deja de sorprendernos en cuanto a su potencia y ventajas en el desarrollo de software de calidad (bajo acomplamiento, alta cohesión, etc.)

Espero que os haya parecido útil este tutorial.

Desde mi punto de vista lo realmente importante es comprender lo que hay detrás de cada tecnología y no convertirse en una simple máquina repetidora de recetas o rellena curriculum… los tutoriales no son libros, sólo se exponen un fragmento de lo que la tecnología permite, así queda en mano de cada uno pasarse horas y horas investigando al respecto.


Carlos García Pérez. Creador de MobileTest, un complemento educativo para los profesores y sus alumnos.

cgpcosmad@gmail.com

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad