Está en la página 1de 10

Tutorial para Maven, Hibernate, Spring y Anotaciones #

Soy de las personas que aprende con pequeños tutoriales y no con extensas explicaciones
llenas de anécdotas, detalles escabrosos y situaciones rebuscadas que jamas se dan en la
practica. Casi todo lo que se sobre desarrollo en los últimos años se lo debo a blogs, pequeños
artículos que apenas superan las 5 páginas y supongo que no debo ser el único con esta
metodología. Lo importante es hacer un esfuerzo por entender lo que hay detrás de cada
tecnología y no convertirse en una simple maquina repetidora de recetas, algo en lo que caemos
con frecuencia aquellos que participamos en proyectos de desarrollo a diario, pero que es
evitable si buscamos buenas fuentes de información y hacemos un esfuerzo para que todo lo
que hacemos tenga lógica y sentido practico.

Una de las cosas que me ha tenido ocupado en los últimos días es crear un pequeño ejemplo
introductorio para varias tecnologías: Maven2, Hibernate, Spring, JUnit, Log4j, etc. La idea era
tener un punto de partida que integre todas estas cosas y que pueda ser entendido por personas
que se están iniciando en el tema. Aquí va.

Código usado para este tutorial -> ejemploSpring.tar.gz

Creación de un proyecto con Maven #

Maven se autodefine como un software para la gestión de proyectos y ataca varios puntos en el
proceso de desarrollo . La primera cosa importante que le pediremos hacer a Maven es que nos
permita construir un proyecto simple. Para esto utilizaremos el siguiente comando:

mvn archetype:create -DarchetypeGroupId=org.apache.maven.archetypes -DgroupId=cl.


ariel.ejemplo -DartifactId=ejemplo

Básicamente lo que Maven hace por nosotros es crear una estructura de directorios y archivos
para un proyecto simple (existen muchos "arquetipos" disponibles para proyectos más
complejos). El directorio creado se ve así:

.
`-- ejemplo
|-- pom.xml
`-- src
|-- main
| `-- java
| `-- cl
| `-- ariel
| `-- ejemplo
| `-- App.java
`-- test
`-- java
`-- cl
`-- ariel
`-- ejemplo
`-- AppTest.java

En la medida que nuestro proyecto avanza irán apareciendo otros archivos, pero esta estructura
es un buen comienzo. Al ver el comando que usamos para crear el proyecto existen 2
parámetros importantes: groupId es un texto libre que nos permite dar alguna estructura
jerárquica a nuestros proyectos, algo muy parecido a lo que hacemos con los packages dentro
del código y por lo mismo resulta recomendable usar la misma nomenclatura (sin ir mas lejos, el
arquetipo crea ciertas clases por nosotros usando ese mismo supuesto); el otro parámetro es
artifactId y suele ser el nombre mismo del proyecto o modulo.

Una gran diferencia entre Maven y ANT (la otra herramienta para construcción ampliamente
utilizada en Java) es que Maven sabe hacer muchas cosas por omisión, mientras que ANT
requiere configuración incluso para el proyecto más simple. Un archivo pom.xml prácticamente
vacío ya puede compilar, empaquetar, generar javadoc, testear un proyecto tan solo si seguimos
ciertas reglas en la estructura de directorios del mismo. El ejemplo que estamos desarrollando se
ve de la siguiente forma:

.
|-- pom.xml
`-- src
|-- main
| `-- java
| `-- cl
| `-- ariel
| `-- ejemplo
| |-- SpringContext.java
| |-- dao
| | |-- HibernatePersonaDao.java
| | `-- PersonaDao.java
| |-- model
| | `-- Persona.java
| `-- service
| |-- PersonaManager.java
| `-- PersonaManagerImpl.java
`-- test
|-- java
| `-- cl
| `-- ariel
| `-- ejemplo
| `-- service
| `-- PersonaManagerTest.java
`-- resources
|-- log4j.properties
`-- spring-config.xml

El archivo pom.xml nos permite configurar la construcción del proyecto. Esta separado en varias
partes en las cuales podemos definir propiedades que luego serán utilizadas en otras partes del
archivos. He intentado explicar el significado de cada tag dentro del mismo archivo así que no
repetiré esas cosas en este documento. Debo destacar eso si otra de las funcionalidades más
extraordinarias de Maven: resolución de dependencias. Una dependencia es una o varias
bibliotecas de clases y/o recursos de los cuales depende nuestro proyecto y que en condiciones
normales debemos bajar de Internet/Intranet manualmente y dejar disponibles para la
compilación. Maven nos ofrece la posibilidad de obtener dichas dependencias por nosotros y
más importante aun, resolver las dependencias de nuestras dependencias .. y las dependencias
de las dependencias de las dependencias (supongo que se entiende la idea :) ). Lo único que
debemos hacer nosotros es definir que cosas necesita nuestro proyecto de la siguiente forma:

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate</artifactId>
<version>3.2.6.ga</version>
</dependency>

La primera duda que seguramente surgirá es de donde sacamos esta información?? Yo se que
necesito Hibernate, como se que groupId y que artifactId debo declarar? Existen 2 respuestas
posibles para esto; la primera es que un IDE con buen soporte para Maven es de gran ayuda
(por ejemplo, Netbeans 6.1) ya que al estar editando el archivo pom.xml típicamente nos
ofrecerá completar dichos valores; la segunda opción es ir al sitio http://mvnrepository.com/,
buscar por el nombre del proyecto y usar la sugerencia que nos da el buscador.

Hay muchas cosas mas que pueden hablarse de Maven. Hay plugins que permiten hacer
"deploy" de aplicaciones en casi cualquier contenedor, generar esquemas de base de datos a
partir de las clases Java para Hibernate o simplemente crear un sitio para nuestro proyecto. Este
ultimo caso no requiere ni siquiera configuración así que me parece entretenido mencionarlo. Por
linea de comando es simplemente:

mvn site:site

Primeros pasos con Hibernate #

Lo primero que necesitamos definir es un objeto que mapea con una entidad en la base de
datos. En el Hibernate "tradicional" esto se hacia con clases POJO ("Plain Old Java Object" ) y
archivos XML, pero desde el surgimiento de JPA ("Java Persistence API") ahora también
podemos usar anotaciones como reemplazo del XML. Es así como una clase representante de
una entidad persistente se puede ver de la siguiente forma:

package cl.ariel.ejemplo.model;

import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "persona")
public class Persona implements Serializable {

@Id
@Column(name = "rut")
private int rut = 0;
@Column(name = "nombre")
private String nombre = null;
@Column(name = "apellido_paterno")
private String apellidoPaterno = null;
@Column(name = "apellido_materno")
private String apellidoMaterno = null;

public Persona(int rut, String nombre, String apellidoPaterno,


String apellidoMaterno) {
this.rut = rut;
this.apellidoMaterno = apellidoMaterno;
this.apellidoPaterno = apellidoPaterno;
this.nombre = nombre;
}

public Persona() {
// Constructor vacio
}

public String getNombre() {


return nombre;
}

public void setNombre(String nombre) {


this.nombre = nombre;
}

public String getApellidoPaterno() {


return apellidoPaterno;
}

public void setApellidoPaterno(String apellidoPaterno) {


this.apellidoPaterno = apellidoPaterno;
}

public String getApellidoMaterno() {


return apellidoMaterno;
}

public void setApellidoMaterno(String apellidoMaterno) {


this.apellidoMaterno = apellidoMaterno;
}

public int getRut() {


return rut;
}

public void setRut(int rut) {


this.rut = rut;
}

@Override
public String toString() {
return this.nombre + " " + this.apellidoPaterno + " " +
this.apellidoMaterno + " (" + this.rut + ")";
}

@Override
public int hashCode() {
return this.rut;
}

@Override
public boolean equals(Object obj) {
boolean result = false;
if (obj != null && getClass() == obj.getClass()) {
final Persona other = (Persona) obj;
if (this.rut == other.rut) {
result = true;
}
}
return result;
}
}

Este no pretende ser un tutorial sobre las anotaciones JPA así que solo nombraré las más
importantes. @Entity nos indica que la clase que estamos definiendo mapea con una entidad
persistente (i.e. una entidad/tabla en el modelo de datos Entidad/Relación); @Table, @Column y
@Id nos permiten definir nombre de la tabla, nombre de las columnas y llave primaria
respectivamente. En ejemplos más avanzados tendremos anotaciones que permiten definir
campos autoincrementales, uuid, nulicidad, relaciones uno-muchos, etc.

Un detalle que resulta importante mencionar es la implementación de equals() y hashcode().


Para llaves primarias propias (es decir, no autogeneradas) la implementación más simple es que
el método equals() use directamente ese valor para resolver la igualdad y para el caso contrario
debemos tener algún criterio de negocio que sea consistente con el tipo del objeto (en este caso,
por ejemplo, algo así como nombre + apellidos). Por otro lado, el método haschode() siempre
debe cumplir con una condición básica: si equals() retorna true entre 2 objetos entonces los
hashcode's deben ser iguales; no existe otra condición que deba cumplirse aunque esta lleno de
literatura sobre buenas formas de escribir este método. La recomendación básica es: escribir un
método hashcode() usando los mismos campos del método equals() y/o usar
[http]HashCodeBuilder y [http]EqualsBuilder del proyecto [http]commons-lang de Apache.

Implementación de un DAO #

El patrón de diseño DAO ("Data Access Object") nos permite separar la forma en que
accedemos a los datos de la fuente de datos misma. La practica dice que esto no es
extraordinariamente común pero otro buen argumento para separar estas cosas es la posibilidad
de hacer testing de nuestra lógica de negocio sin necesidad de una fuente de datos (es decir,
creando implementaciones "mula" de los DAO's para los servicios). En la practica un DAO no
suele ser más que una interfaz con operaciones basicas (típicamente llamados CRUD - "Create,
Read, Update and Delete - sobre nuestros objetos de entidad). En este caso algo así:

package cl.ariel.ejemplo.dao;

import cl.ariel.ejemplo.model.Persona;
import java.util.List;

public interface PersonaDao {


public void saveOrUpdate(Persona persona);
public void delete(Persona persona);
public void find(int rut);
public List<Persona> findAll();
}

Aun cuando podríamos implementar este DAO para Hibernate directamente, Spring nos provee
una clase llamada ?HibernateDaoSupport que encapsula buena parte de las cosas que debemos
hacer para integrar ambos frameworks.
package cl.ariel.ejemplo.dao;

import cl.ariel.ejemplo.model.Persona;
import java.util.List;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.stereotype.Service;

@Service(value="personaDAO")
public class HibernatePersonaDao extends HibernateDaoSupport implements
PersonaDao {

@Autowired
public HibernatePersonaDao(@Qualifier("sessionFactory") SessionFactory
sessionFactory) {
this.setSessionFactory(sessionFactory);
}

public void saveOrUpdate(Persona persona) {


this.getHibernateTemplate().saveOrUpdate(persona);
}

public void delete(Persona persona) {


this.getHibernateTemplate().delete(persona);
}

public void find(int rut) {


this.getHibernateTemplate().load(Persona.class, rut);
}

public List<Persona> findAll() {


return this.getHibernateTemplate().find("from Persona");
}

La primera cosa digna de destacar en este código es la existencia de 3 anotaciones nuevas.


@Service le dice a Spring que queremos darle un nombre a esta clase para que sea instanciada
automáticamente por el framework cada vez que usemos el identificador "personaDAO" ya sea
en una inyección de dependencia o en una llamada explicita a
contextoSpring.getbean("personaDAO"). Para quienes tengan experiencia previa con Spring el
resumen es que estamos reemplazando lo siguiente:

<bean name="personaDAO" class="cl.ariel.ejemplo.dao.HibernatePersonaDao">


<property name="sessionFactory" ref="sessionFactory" />
</bean>

en el archivo spring-context.xml por una anotación. Por otro lado, @Autowired nos permite
indicarle a Spring que el constructor que estamos declarando requiere de una inyección de
dependencias, en este caso sessionFactory. Como sabe Spring donde inyectar el objeto y que
poner ahí? Pues bien, la anotación @Qualifier precede al parámetro del constructor y con eso
indicamos donde debe ser inyectada la dependencia, mientras que el valor de la anotación indica
como se llama el objeto que Spring debe inyectar. Más adelante aclararemos que es un "Session
Factory".

Servicios y/o Managers #

Hasta ahora lo que tenemos son los objetos de persistencia y operaciones básicas. El siguiente
paso es tener un lugar donde colocar nuestra logica de negocio y no menos importante, un lugar
donde podamos declarar las transacciones de nuestro sistema (léase, operaciones CRUD
agrupadas dentro de un mismo bloque commit/rollback). En la literatura de Spring estos objetos
suelen ser llamados "Service", pero otros los conocen como "Manager". En nuestro ejemplo la
interfaz que define nuestro servicio es la siguiente:

package cl.ariel.ejemplo.service;

import cl.ariel.ejemplo.model.Persona;
import java.util.List;

public interface PersonaManager {


public void insertarPersonas(List<Persona> personas);
public List<Persona> obtenerPersonas();
}

Y la implementación usando anotaciones de Spring queda de la siguiente forma:

package cl.ariel.ejemplo.service;

import cl.ariel.ejemplo.dao.PersonaDao;
import cl.ariel.ejemplo.model.Persona;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service(value="personaManager")
public class PersonaManagerImpl implements PersonaManager {

@Resource(name="personaDAO")
private PersonaDao personaDAO = null;

public PersonaDao getPersonaDAO() {


return personaDAO;
}

public void setPersonaDAO(PersonaDao personaDAO) {


this.personaDAO = personaDAO;
}

@Transactional(propagation = Propagation.REQUIRED)
public void insertarPersonas(List<Persona> personas) {
if (personas != null) {
for (Persona persona : personas) {
this.personaDAO.saveOrUpdate(persona);
}
}
}

public List<Persona> obtenerPersonas() {


return this.personaDAO.findAll();
}
}

Dos anotaciones nuevas en este punto. @Resource le dice a Spring que debe buscar el recurso
"personaDAO" e inyectarlo a mi objeto; es en la practica un equivalente de @Autowired y
@Qualifier pero en una sola línea. La otra anotación que encontramos es @Transactional y es la
que nos permite definir un método o una clase que agrupa varias operaciones que deben ser
commiteadas o rollbackeadas en conjunto.

Configuración de Spring #

Básicamente en el archivo spring-context.xml tenemos toda la configuración necesaria para


Spring. Cosas dignas de mencionar:

<!-- Declaramos el uso de anotaciones para Spring -->


<context:annotation-config/>
<!-- Indicamos que clases deben ser leidas por Spring para buscar recursos -->
<context:component-scan base-package="cl.ariel.ejemplo" />

Nos permite definir bean's usando @Service e inyección de dependencia con @Resource,
@Autowired y @Qualifier.

<!-- Soporte para transacciones via anotaciones -->


<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />

Nos permite definir transacciones usando @Transactional dentro de un método.

Probando nuestro código #

Para ver como funciona el código una buena opción es acostumbrarse a usar JUnit (u otro
framework de unit testing). En mi ejemplo solo intento intertar 2 personas y luego traerlas de
vuelta.

package cl.ariel.ejemplo;

import cl.ariel.ejemplo.service.PersonaManager;
import cl.ariel.ejemplo.SpringContext;
import cl.ariel.ejemplo.model.Persona;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
import org.junit.Test;
import static org.junit.Assert.assertTrue;

public class PersonaManagerTest {

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


@Test
public void testInsertaPersona() throws Exception {

logger.debug("Creando contexto spring");


// Obtenemos el contexto Spring
SpringContext context = SpringContext.getInstance();

// Creo mis objetos


Persona persona1 = new Persona(12651196, "Ariel", "Aguayo", "Bascuñan");
logger.debug("persona1 = " + persona1);
Persona persona2 = new Persona(1, "Perico", "Palotes", "Moya");
logger.debug("persona2 = " + persona2);

List<Persona> personas = new ArrayList<Persona>();


personas.add(persona1);
personas.add(persona2);
logger.debug("personas = " + personas);

// Obtengo el Manager
PersonaManager personaManager = (PersonaManager)context.
getBean("personaManager");

// Inserto
personaManager.insertarPersonas(personas);

// Obtengo la lista le personas


List<Persona> listaPersonas = personaManager.obtenerPersonas();

logger.debug("personas retornadas = " + listaPersonas);

// Verifico que la persona existe


assertTrue(listaPersonas.contains(persona1) && listaPersonas.
contains(persona2));
}
}

La anotación @Test le indica a JUnit que ese método debe ser ejecutado cuando deseamos
probar nuestro proyecto. Por otro lado, el framework nos provee de un conjunto de "assert's"
para validar el éxito o fracaso de nuestro test. En este caso simplemente deseo validar que las
personas insertadas están presentes en la lista que retorna la base de datos (usando en método
contains() de Collection).

Dos cosas faltan para terminar: SpringContext es una clase utilitaria que me permite acceder a
los bean's de Spring desde mi codigo. Es un singleton bastante simple y se ve de la siguiente
forma:

package cl.ariel.example;

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

public class SpringContext {

private static ApplicationContext context;


private static SpringContext instance;
private final static String CONFIG_FILE = "spring-config.xml";

public static SpringContext getInstance() {


if (null == instance) {
instance = new SpringContext();
context = new ClassPathXmlApplicationContext(
CONFIG_FILE);
}
return instance;
}

public Object getBean(String bean) {


return context.getBean(bean);
}
}

Y un archivo para configurar log4j (Nuestro framework para logging):

log4j.appender.NULL_APPENDER=org.apache.log4j.varia.NullAppender
log4j.rootLogger=FATAL, NULL_APPENDER

log4j.appender.DEBUG_APPENDER=org.apache.log4j.RollingFileAppender
log4j.appender.DEBUG_APPENDER.layout=org.apache.log4j.PatternLayout
log4j.appender.DEBUG_APPENDER.layout.ConversionPattern=[%-5p %d{dd/MM/yyyy
hh:mm:ss,SSS}] %l - %m%n
log4j.appender.DEBUG_APPENDER.ImmediateFlush=true
log4j.appender.DEBUG_APPENDER.File=/tmp/debug_springExample.log
log4j.appender.DEBUG_APPENDER.Append=true
log4j.appender.DEBUG_APPENDER.Threshold=DEBUG
log4j.appender.DEBUG_APPENDER.MaxFileSize=10000KB
log4j.appender.DEBUG_APPENDER.MaxBackupIndex=10

También podría gustarte