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

Master your semester with Scribd & The New York Times

Special offer for students: Only $4.99/month.

Master your semester with Scribd & The New York Times

Cancel anytime.