Ejemplo de listener de eventos de Hibernate

Escrito por el , actualizado el .
java planeta-codigo programacion
Enlace permanente Comentarios

Java

En alguna ocasión puede que tengamos la necesidad de realizar alguna acción cuando una entidad es guardada en base de datos, actualizada, eliminada, cargada, … . Algunos casos de uso pueden ser:

  • Establecer la fecha de creación o de actualización del objeto cuando es persistido en base de datos.
  • Cifrar un dato del objeto al ser persistido en base de datos.
  • Proporcionar seguridad de modo que un usuario solo pueda modificar o acceder a los objetos a los que tenga acceso.
  • Al persistir el objeto guardar en un campo el valor calculado resultado de una función.
  • Cualquier otra restricción, cálculos o acciones.

Para realizar estas operaciones podemos hacerlas de varias formas, una puede ser usando triggers de la base de datos disponibles en Oracle y PostgreSQL. Si usamos Hibernate como librería ORM de persistencia en una aplicación el equivalente a los triggers de BBDD es mediante un listener que sea llamado al ocurrir ciertos eventos. En la clase EventType está la lista completa de los eventos que podemos recibir y la clase listener que debemos implementar para recibir cada uno de los eventos. Esta es la lista de los eventos disponibles según los conceptos que usa Hibernate:

  • AUTO_FLUSH
  • CLEAR
  • DELETE
  • DIRTY_CHECK
  • EVICT
  • FLUSH
  • FLUSH_ENTITY
  • INIT_COLLECTION
  • LOAD
  • LOCK
  • MERGE
  • PERSIST
  • PERSIST_ONFLUSH
  • POST_COLLECTION_RECREATE
  • POST_COLLECTION_REMOVE
  • POST_COLLECTION_UPDATE
  • POST_COMMIT_DELETE
  • POST_COMMIT_INSERT
  • POST_COMMIT_UPDATE
  • POST_DELETE
  • POST_INSERT
  • POST_LOAD
  • POST_UPDATE
  • PRE_COLLECTION_RECREATE
  • PRE_COLLECTION_REMOVE
  • PRE_COLLECTION_UPDATE
  • PRE_DELETE
  • PRE_INSERT
  • PRE_LOAD
  • PRE_UPDATE
  • REFRESH
  • REPLICATE
  • RESOLVE_NATURAL_ID
  • SAVE
  • SAVE_UPDATE
  • UPDATE

Con estos eventos podemos ser notificados de muchas cosas que suceden internamente en Hibernate en algunos casos antes y/o después del evento. En JPA se dispone de varias anotaciones (@PreInsert, @PostInsert, …) con las que podemos marcar un determinado método como listener de un evento pero no funcionan si usamos únicamente Hibernate.

En este artículo explicaré como implementar un listener de ejemplo que reciba parte de estos eventos usando solo Hibernate. Primeramente e importante, debemos tener en cuenta que el proceso como reacción a uno de estos eventos ha de ser muy ligero y tardar poco tiempo ya que algunos eventos son lanzados por cada instancia de entidad como consecuencia de operaciones muy frecuentes en una aplicación, si tardasen mucho o consumiesen mucha memoria o tiempo de procesador probablemente el rendimiento de la aplicación disminuiría notablemente.

Como se ve en la clase EventType cada evento tiene un listener distinto, para evitar crear una clase diferente por cada listener podemos emplear el patrón de diseño Adapter de forma que implemente las diferentes interfaces en las que estamos interesados. La implementación de la clase Adapter y una implementación de esta clase Adapter si nos interesasen los eventos PRE_INSERT, PRE_UPDATE, PRE_DELETE, POST_INSERT, POST_UPDATE, POST_DELETE sería la siguiente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package es.com.blogspot.elblogdepicodev.plugintapestry.services.hibernate;

import org.hibernate.event.spi.PostDeleteEvent;
import org.hibernate.event.spi.PostDeleteEventListener;
import org.hibernate.event.spi.PostInsertEvent;
import org.hibernate.event.spi.PostInsertEventListener;
import org.hibernate.event.spi.PostUpdateEvent;
import org.hibernate.event.spi.PostUpdateEventListener;
import org.hibernate.event.spi.PreDeleteEvent;
import org.hibernate.event.spi.PreDeleteEventListener;
import org.hibernate.event.spi.PreInsertEvent;
import org.hibernate.event.spi.PreInsertEventListener;
import org.hibernate.event.spi.PreUpdateEvent;
import org.hibernate.event.spi.PreUpdateEventListener;
import org.hibernate.persister.entity.EntityPersister;

public class HibernateEventAdapter implements PreInsertEventListener, PostInsertEventListener, PreUpdateEventListener, PostUpdateEventListener, PreDeleteEventListener,
		PostDeleteEventListener {

	private static final long serialVersionUID = 1L;

	@Override
	public boolean requiresPostCommitHanding(EntityPersister persister) {
		return false;
	}

	@Override
	public void onPostDelete(PostDeleteEvent event) {
	}

	@Override
	public boolean onPreDelete(PreDeleteEvent event) {
		return false;
	}

	@Override
	public void onPostUpdate(PostUpdateEvent event) {
	}

	@Override
	public boolean onPreUpdate(PreUpdateEvent event) {
		return false;
	}

	@Override
	public void onPostInsert(PostInsertEvent event) {
	}

	@Override
	public boolean onPreInsert(PreInsertEvent event) {
		return false;
	}
}
HibernateEventAdapter.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package es.com.blogspot.elblogdepicodev.plugintapestry.services.hibernate;

import org.hibernate.event.spi.PostDeleteEvent;
import org.hibernate.event.spi.PostInsertEvent;
import org.hibernate.event.spi.PostUpdateEvent;
import org.hibernate.event.spi.PreDeleteEvent;
import org.hibernate.event.spi.PreInsertEvent;
import org.hibernate.event.spi.PreUpdateEvent;
import org.springframework.beans.factory.annotation.Autowired;

import es.com.blogspot.elblogdepicodev.plugintapestry.services.spring.DummyService;

public class ProductoEventAdapter extends HibernateEventAdapter {

	private static final long serialVersionUID = 1L;

	@Autowired
	private DummyService dummy;
	
	public void setDummy(DummyService dummy) {
		this.dummy = dummy;		
	}
	
	@Override
	public void onPostDelete(PostDeleteEvent event) {
		dummy.process("postDelete", event.getEntity());
	}

	@Override
	public boolean onPreDelete(PreDeleteEvent event) {
		dummy.process("preDelete", event.getEntity());
		return false;
	}

	@Override
	public void onPostUpdate(PostUpdateEvent event) {
		dummy.process("postUpdate", event.getEntity());
	}

	@Override
	public boolean onPreUpdate(PreUpdateEvent event) {
		dummy.process("preUpdate", event.getEntity());
		return false;
	}

	@Override
	public void onPostInsert(PostInsertEvent event) {
		dummy.process("postInsert", event.getEntity());
	}

	@Override
	public boolean onPreInsert(PreInsertEvent event) {
		dummy.process("preInsert", event.getEntity());
		return false;
	}
}
ProductoEventAdapter.java

Una vez que tenemos la clase que va a recibir los eventos para que Hibernate la use debemos crear un Integrator que lo instanciará y la dará a conocer a Hibernate. En el siguiente código puede verse una implementación de un Integrator de Hibernate, en el se instancia el listener y se asocia a los diferentes eventos. En este caso solo se crea un listener pero perfectamente podríamos asociar varios listeners al mismo evento:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package es.com.blogspot.elblogdepicodev.plugintapestry.services.hibernate;

import org.hibernate.cfg.Configuration;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.EventType;
import org.hibernate.integrator.spi.Integrator;
import org.hibernate.metamodel.source.MetadataImplementor;
import org.hibernate.service.spi.SessionFactoryServiceRegistry;

import es.com.blogspot.elblogdepicodev.plugintapestry.services.spring.DummyService;

public class HibernateIntegrator implements Integrator {

	@Override
	public void integrate(Configuration configuration, SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
        // As you might expect, an EventListenerRegistry is the place with which event listeners are registered. It is a service
        // so we look it up using the service registry
        final EventListenerRegistry eventListenerRegistry = serviceRegistry.getService(EventListenerRegistry.class);

        ProductoEventAdapter pea = new ProductoEventAdapter();
        pea.setDummy(new DummyService());
        
        eventListenerRegistry.setListeners(EventType.PRE_INSERT, pea);
        eventListenerRegistry.setListeners(EventType.PRE_UPDATE, pea);
        eventListenerRegistry.setListeners(EventType.PRE_DELETE, pea);
        eventListenerRegistry.setListeners(EventType.POST_INSERT, pea);
        eventListenerRegistry.setListeners(EventType.POST_UPDATE, pea);
        eventListenerRegistry.setListeners(EventType.POST_INSERT, pea);
	}

	@Override
	public void integrate(MetadataImplementor metadata, SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
	}

	@Override
	public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
	}
}
HibernateIntegrator.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package es.com.blogspot.elblogdepicodev.plugintapestry.services.spring;

import es.com.blogspot.elblogdepicodev.plugintapestry.entities.Producto;

public class DummyService {

	public void process(String action, Object entity) {
		if (entity instanceof Producto) {
			Producto p = (Producto) entity;
			System.out.println(String.format("Action: %s, Id: %d", action, p.getId()));
		}
	}
}
DummyService.java

Finalmente, para que Hibernate conozca la existencia de este Integrator debemos crear un archivo que contenga el nombre completo de la clase Integrator. El archivo ha de estar en de un librería .jar en la ubicación /META-INF/services/org.hibernate.integrator.spi.Integrator y disponible en el classpath. El contenido de este archivo para el ejemplo es:

1
2
es.com.blogspot.elblogdepicodev.plugintapestry.services.hibernate.HibernateIntegrator

Integrator

Con esto ya recibiremos los eventos cuando ocurran. En el ejemplo aparecerá en la consola los mensajes cuando se inserte, actualice o elimine una fila de base de datos. En las capturas de imagen se muestran las trazas de una inserción, una traza para la preinseción Action: preInsert, Id: null donde se ve que la entidad no tienen identificativo asignado y otra traza después de la inserción Action: postInsert, Id: 1 donde la entidad ya tiene identificativo asignado y la sentencia SQL se ha ejecutado, como se ve en la captura los mensajes salen antes y después de ejecutarse la sentencia SQL que se envía a la base de datos.

Creando un registro en la base de datos Trazas de ejecución del listener

Otra implementación distinta a la expuesta en este artículo es con anotaciones tal y como hace JPA, podríamos hacer una implementación de listener que busque una anotación en la entidad y llame a ese método cuando se produzca el evento. Depende de como prefiramos organizar el código, si preferimos tener el código del listener separado de la entidad o todo el código en la propia entidad.

Esto así puede servirnos pero si el listeners es más complejo debamos hacer uso de un servicio de Spring, en el ejemplo mostrado se usa la clase DummyService.java que es instanciada por HibernateIntegrator.java e inyectada en la clase adaptador ProductoEventAdapter.java. En el siguiente artículo explicaré lo que debemos hacer para crear un listener de Hibernate que use servicios de Spring e inyecte dependencias de otros servicios, de esta forma el listener o adaptador podrá usar todas las funcionalidades de los servicios disponibles en el contenedor IoC de Spring.

El código fuente completo del ejemplo lo puedes encontrar en mi repositorio de GitHub.


Comparte el artículo: