Eventos en Hibernate (parte II)

0
16932

Eventos en Hibernate (Parte II)

Índice de contenidos

  1. Introducción

  2. Entorno

  3. Reconfigurando la aplicación de ejemplo

  4. Un DAO nuevo

  5. Configuración de Hibernate

  6. Probar de nuevo la aplicación

  7. Configurar eventos

  8. Añadir nuestros propios oyentes

  9. Diferentes formas de gestionar los eventos

  10. Conclusiones

1. Introducción

Este tutorial es continuación del tutorial «Eventos
en
Hibernate (Parte I)»
. Aquí vamos a centrarnos en el uso de los
eventos definidos en la
especificación EJB3 gracias a Hibernate Entity Manager.
Podemos asociar métodos de retrollamada a diferentes eventos
de
forma que se
ejecuten de manera automática. Por
ejemplo, esto es muy útil para calcular el valor de campos de
nuestros objetos que no se persisten en la base de datos, pero se
pueden calcular en función de otros; podemos asignar un
método de retrollamada para que se ejecute al cargar la
entidad y calcule el valor de dicho campo.

En este tutorial vamos a continuar con el ejemplo que vimos
anteriormente, en el cual teníamos una entidad usuarios con
un campo edad no persistido, que se calculará
automáticamente a partir de la fecha de nacimiento.

2. Entorno

Para la realización de este tutorial se han utilizado
las siguientes herramientas:

  • Hardware: Portátil Asus G50Vseries (Core Duo
    P8600 2.4GHz, 4GB RAM, 320 GB HD).

  • Sistema operativo: Windows Vista Ultimate.

  • Eclipse Ganymede 3.4.1, con el plugin Q (http://code.google.com/p/q4e)
    para Maven

  • JDK 1.6.0

  • Maven 2.0.9

  • MySql 5.1.30

  • Hibernate 3

3. Reconfigurando la
aplicación de ejemplo

Para poder utilizar un EntityManager debemos añadir la
siguiente dependencia al fichero pom.xml.


    org.hibernate
    hibernate-entitymanager
    3.4.0.GA

4. Un DAO nuevo

El Dao que habíamos utilizado en el ejemplo anterior
no nos
vale ahora, ya que utilizaba una Session de Hibernate para manejar
nuestras entidades. Si queremos tener toda la potencia de las
anotaciones EJB3 debemos utilizar un EntityManager, que es equivalente
a Session pero amplía sus capacidades
permitiéndonos
utilizar toda la semántica de EJB3.

Es importante realizar las operaciones de persistencia dentro
de una transacción para poder hacer COMMIT. De lo contrario
por defecto se hará ROLLBACK y nuestras acciones no
tendrán el efecto esperado. Nosotros mismos deberemos
controlar en nuestro código la creación del
EntityManager y el inicio y fin de las transacciones, ya que nuestra
aplicación va a ejecutarse fuera de un contenedor J2EE.

package modelo;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;


public class UsuarioEntityManagerDao {

	public void guardarUsuario(Usuario usuario) {
		EntityManagerFactory emf = Persistence.createEntityManagerFactory("manager1");
		EntityManager em = emf.createEntityManager();
		EntityTransaction tx = null;

		try {
			tx = em.getTransaction();
			tx.begin();
			em.persist(usuario);
			tx.commit();
		} finally {
			em.close();
			emf.close();
		}
	}
	
	public List buscarUsuarios() {
		EntityManagerFactory emf = Persistence.createEntityManagerFactory("manager1");
		EntityManager em = emf.createEntityManager();
		EntityTransaction tx = null;
		List usuarios = new ArrayList();
		
		try {
			tx = em.getTransaction();
			tx.begin();
			usuarios = em.createQuery("from Usuario").getResultList();
			tx.commit();
		} finally {
			em.close();
			emf.close();
		}

		return usuarios;
	}	
}

5. Configuración
de Hibernate

Para crear el EntityManager, hemos utilizado la clase
Persistence, pasándole al método
«createEntityManagerFactory» un nombre como parámetro. Este
nombre identificará una unidad de persistencia en la que
definiremos la configuración que será utilizada para
crear el EntityManager. Este fichero se llamará
«persistence.xml» y deberá ponerse dentro de la carpeta
«META-INF», porque es allí donde lo
buscará la EntityManagerFactory. El atributo name de
la unidad de persistencia debe coincidir con el que le pasamos a la
EntityManagerFactory.

El tipo de transacción se ha puesto como
«RESOURCE_LOCAL». Esta es la única posibilidad que tenemos
para un simple proyecto Java, pero si dispusiéramos de un
contenedor J2EE podríamos utilizar «JTA», lo cual nos
evitaría tener que iniciar por nosotros mismos las
transacciones.


	
		modelo.Usuario
		
			
			
			
			
			
			
			
			
			
		
	


Una alternativa a poner la configuración de Hibernate
en el fichero «persistence.xml» consiste en indicar un fichero de
configuración de hibernate de donde cogerla (la línea
que aparece comentada en el código anterior). Podemos incluso
mezclar ambas configuraciones, pero en caso de conflicto siempre
prevalecerá la que pongamos en el fichero «persistence.xml».

6. Probar de nuevo la
aplicación

Ahora sólamente nos queda probar la
aplicación que hemos rehecho, para ver si funciona como es
debido. Para ello modificamos nuestras dos clases de prueba para que
utilicen el nuevo Dao.

Aquí está el código modificado de
nuestra
clase PruebaInsercion.

package prueba;

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

import modelo.Usuario;
import modelo.UsuarioEntityManagerDao;

public class PruebaInsercion {
	public static void main(String[] args) {
		Calendar cal = new GregorianCalendar();
		cal.set(1970, 10, 5);
		nuevoUsuario("Juan", "Lopez", cal.getTime());
		cal.set(1976, 3, 16);
		nuevoUsuario("Enrique", "Perez", cal.getTime());
		cal.set(1973, 5, 9);
		nuevoUsuario("Laura", "Iglesias", cal.getTime());
	}

	private static void nuevoUsuario(String nombre, String apellidos, Date edad) {
		UsuarioEntityManagerDao dao = new UsuarioEntityManagerDao();
		Usuario usuario = new Usuario(nombre, apellidos);
		usuario.setNacimiento(edad);
		dao.guardarUsuario(usuario);
	}
}

Y aquí tenemos la clase PruebaListado, que utiliza
también el nuevo Dao.

package prueba;

import java.util.List;

import modelo.Usuario;
import modelo.UsuarioEntityManagerDao;

public class PruebaListado {
	public static void main(String[] args) {
		UsuarioEntityManagerDao dao = new UsuarioEntityManagerDao();
		List usuarios = dao.buscarUsuarios();
		for (Usuario usuario : usuarios) {
			System.out.printf("%s %s tiene %s años\n", usuario.getNombre(),
					usuario.getApellidos(), usuario.getEdad());
		}
	}
}

Si hemos seguido el tutorial anterior tendremos que borrar la
tabla de nuestro esquema de base de datos, para comprobar que vuelve a
crearse y a cargarse con datos correctos.

Ejecutamos primero la inserción y comprobamos que se
han añadido correctamente los usuarios a nuestra base de datos:

Consulta de usuarios en la base de datos

Ahora ejecutamos la prueba de listado y observamos…
¡Vaya, esto me suena! Lo hemos vuelto a hacer, ¿no
teníamos que capturar el evento?.

Listado de usuarios con edad igual a cero

7. Configurar eventos

Este es el apartado que más me gusta del tutorial,
porque sólamente tenemos que escribir una palabra: @PostLoad.
Esta anotación delante de nuestra función
«calcularEdad» es todo lo que tenemos que hacer para que nuestra
función sea llamada después de cargar los datos de la
entidad. Deberemos también añadir el import necesario.

...

import javax.persistence.PostLoad;

...

@PostLoad
public void calcularEdad() {
Calendar cumple = new GregorianCalendar(); Calendar ahora = new
GregorianCalendar();

...

Si volvemos a ejecutar la función que lista los
usuarios, obtendremos el resultado esperado:

Listado de usuarios con edades correctas

A continuación se muestran todas las anotaciones que
pueden utilizarse para que los métodos de la entidad sean
llamados de forma automática al producirse un evento en su
ciclo de vida. Los métodos anotados de esta manera se llaman
«Callback Methods» o métodos de retrollamada.

Podemos asignar varias anotaciones para un mismo
método, y designar varios métodos de retrollamada en
una misma entidad. Pero no podremos asignar dos métodos
diferentes para el mismo evento.

Tipo Descripción Interfaz del oyente
@PrePersist Ejecutado antes de realizar la persistencia (INSERT)
del objeto. 
org.hibernate.event.PreInsertEventListener
@PreRemove Ejecutado antes de realizar el borrado (DELETE) del
objeto.
org.hibernate.event.PreDeleteEventListener
@PostPersist Ejecutado después de realizar la persistencia
del objeto.
org.hibernate.event.PostInsertEventListener
@PostRemove Ejecutado después de realizar el borrado del
objeto.
org.hibernate.event.PostDeleteEventListener
@PreUpdate Ejecutado antes de la actualización (UPDATE)
del objeto.
org.hibernate.event.PreUpdateEventListener
@PostUpdate Ejecutado después de la actualización
del objeto.
org.hibernate.event.PostUpdateEventListener
@PostLoad Ejecutado después de la carga o refresco del
objeto.
org.hibernate.event.PostLoadEventListener

8. Añadir nuestros propios oyentes

La anotaciones anteriores asignan los oyentes predeterminados
de Hibernate a los métodos de retrollamada. También
podemos asignar nuestros
propios oyentes anotando la clase con @EntityListeners.
Podremos pasarle como parámetro nuestra clase oyente o un
array
de clases oyentes:

@EntityListeners(value=Oyente.class) ó @EntityListeners(value={Oyente1.class, Oyente2.class, ...})

Las clases oyentes asignadas de esta manera
deberán implementar métodos para cada evento que
quieran manejar, los cuales se marcarán con la
anotación correspondiente. Estos métodos
recibirán como parámetro una instancia de la entidad
sobre la que ocurre el evento.

Vamos a implementar un oyente que rejuvenezca a nuestros
usuarios, pero esta vez cambiando su fecha de nacimiento.

package oyentes;

import java.util.Calendar;
import java.util.GregorianCalendar;

import javax.persistence.PostLoad;

import modelo.Usuario;

public class EJB3LiftingUsuarioOyente {

	@PostLoad
	public void postLoad(Usuario usuario) {
		System.out.println("Quitando 3 años a " + usuario.getNombre());
		Calendar cal = new GregorianCalendar();
		cal.setTime(usuario.getNacimiento());
		cal.add(Calendar.YEAR, 3);
		usuario.setNacimiento(cal.getTime());
	}
}

Para que se ejecute el oyente, tendremos que utilizar la
anotación @EntityListeners en la entidad Usuario:

@Entity
@EntityListeners(value={EJB3LiftingUsuarioOyente.class})
public class Usuario {

...

Si ejecutamos el listado, veremos la siguiente salida:

Lista de usuarios rejuvenecidos

Si nos fijamos en las sentencias update de Hibernate veremos que esto
ha tenido un efecto secundario. Hemos cambiado la fecha de nacimiento
de los usuarios en la base de datos. Recordemos que según la
semántica de EJB3, si tenemos un objeto manejado por el gestor
de persistencia no es necesario hacer persist, merge, update o nada
parecido para guardar los cambios que hagamos al mismo. Nosotros
modificamos el valor de los campos del objeto y el gestor de
persistencia se encargará automáticamente de reflejar
esos cambios en la base de datos. Así que… este nuevo
lifting parece más duradero que el anterior 😉

9. Diferentes formas de gestionar los eventos

Tenemos diferentes maneras de conseguir que un método
se ejecute al producirse un evento para una entidad. En caso de existir
varios métodos a ejecutar, estos se ejecutarán en el
orden siguiente:

  1. A través de la anotación @EntityListeners
    en la entidad o una superclase de la misma. Estos oyentes se
    ejecutarán en el orden en que se añadan al array
    «value». Es posible evitar que se ejecuten los oyentes definidos por la
    anotación @EntityListeners de las superclases utilizando la
    etiqueta @ExcludeSuperclassListeners en una entidad.
  2. Oyentes de las superclases de la entidad definidos en el
    fichero de configuración (primero la superclase de nivel
    superior).
  3. Oyentes de la propia entidad definidos en el fichero de
    configuración.
  4. Métodos de retrollamada (anotaciones @PostLoad…)
    para las superclases de la entidad (primero la de nivel superior).
  5. Métodos de retrollamada para la entidad.

Ya hemos visto cómo utilizar los métodos de
retrollamada y también la anotación @EntityListeners.
También podemos asignar los EntityListeners y los
métodos de retrollamada por medio de un fichero de
configuración xml. Deberemos indicar en nuestro
«persistence.xml» el fichero de mapeo a utilizar:

...

	
		orm.xml
		modelo.Usuario

...

A través del fichero de mapeo podemos asignar oyentes
por defecto para todas las entidades y oyentes particulares para cada
entidad e, incluso, definir los métodos de retrollamada de
nuestra entidad sin utilizar las anotaciones. Por ejemplo, en el
siguiente fichero xml hemos definido para la entidad Usuario el oyente
EJB3LiftingUsuarioOyente y asociado el evento post-load al
método calcularEdad.




    
        
            
        
    
        
    
		
		    
		        
		    
		
		
		
	


Es importante destacar que no pueden definirse dos
métodos de retrollamada diferentes para el mismo evento, por
lo que deberemos eliminar las anotaciones @PostLoad de nuestra entidad
Usuario y de nuestra clase EJB3LiftingUsuarioOyente.

10. Conclusiones

A la vista de los resultados de este tutorial, podemos extraer
las siguientes conclusiones:

  • Un EntityManager nos permite utilizar Hibernate con la
    semántica definida por JPA para la especificación de
    EJB3.

  • Podemos
    mantener nuestra antigua configuración de Hibernate
    añadiendo una
    referencia a la misma en nuestro persistence.xml. A pesar de eso,
    podremos sobreescribir ciertas propiedades en el nuevo fichero de
    configuración.

  • Con el uso de las anotaciones de
    EJB3 es muy sencillo definir métodos de retrollamada para los
    eventos del ciclo de vida de nuestras entidades, y también es
    más sencillo crear nuestras propias clases oyente.

Y eso es
todo por ahora. En el próximo tutorial de esta serie veremos
una forma de utilizar las anotaciones de EJB3 para definir eventos de
retrollamada utilizando sólamente la clase Session de
Hibernate, es decir, sin utilizar un EntityManager.

Una vez más, desde Autentia esperamos que este
tutorial
os
sea de ayuda si os encontráis en la necesidad de ejecutar
eventos en Hibernate.

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