Académique Documents
Professionnel Documents
Culture Documents
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.
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:
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
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() {
// Constructor vacio
}
@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.
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;
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);
}
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".
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;
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;
@Transactional(propagation = Propagation.REQUIRED)
public void insertarPersonas(List<Persona> personas) {
if (personas != null) {
for (Persona persona : personas) {
this.personaDAO.saveOrUpdate(persona);
}
}
}
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 #
Nos permite definir bean's usando @Service e inyección de dependencia con @Resource,
@Autowired y @Qualifier.
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;
// Obtengo el Manager
PersonaManager personaManager = (PersonaManager)context.
getBean("personaManager");
// Inserto
personaManager.insertarPersonas(personas);
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;
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