Los listeners en Hibernate son como los Tiggers en las bases de datos relacionales. En la versión 4 de Hibernate se ha modificado la forma en la que se registran los listeners, lo que implica que la documentación es escasa en caso de querer ampliar la documentación de éste curso.
Un listener es simplemente una función Java que se ejecutará ante un evento que ocurra en Hibernate. Hibernate dispone de gran cantidad de Listeners. Los más importantes se explican en la siguiente tabla:
Evento | Interfaz | Descripción |
---|---|---|
PreInsert | PreInsertEventListener | Se ejecuta antes de la inserción de la entidad |
PreLoad | PreLoadEventListener | Se ejecuta antes de la carga de la entidad |
PreUpdate | PreUpdateEventListener | Se ejecuta antes de la actualización de la entidad |
PreDelete | PreDeleteEventListener | Se ejecuta antes del borrado de la entidad |
PostInsert | PostInsertEventListener | Se ejecuta tras la inserción de la entidad |
PostLoad | PostLoadEventListener | Se ejecuta tras la carga de la entidad |
PostUpdate | PostUpdateEventListener | Se ejecuta tras la actualización de la entidad |
PostDelete | PostDeleteEventListener | Se ejecuta tras el borrado de la entidad |
Antes de la versión 4 de Hibernate los listeners se definían en el fichero hibernate.cfg.xml
; actualmente la forma es un poco más enrevesada.
Lo primero es que ya no es posible definirlos en un fichero de configuración sino que hay que registrarlos a través de una clase que implemente el interfaz org.hibernate.integrator.spi.Integrator. También hay que indicar a hibernate la clase que implementa Integrator.Posteriormente hay que crear las clases listeners que se lanzarán para todas las entidades. En general considero que ha quedado muy complejo y poco práctico la nueva forma de Hibernate así que mediante 2 sencillas clases he simplicado su uso. Paso ahora a explicar cómo usar los listeners de una forma más sencilla.
En el proyecto que vayamos a usar los listeners deberemos hacer los siguientes cambios:
com.fpmislata.persistencia.hibernate.util
com.fpmislata.persistencia.hibernate.util
crear la clase GenericEventListenerImpl
com.fpmislata.persistencia.hibernate.util
crear la clase GenericIntegratorImpl
META-INF
en el directorio src
.META-INF
crea la carpeta services
META-INF/services
crear un fichero con el nombre org.hibernate.integrator.spi.Integrator
org.hibernate.integrator.spi.Integrator
el siguiente texto: com.fpmislata.persistencia.hibernate.util.GenericIntegratorImpl
El código fuente de las 2 clases Java que hay que crear es el siguiente:
package com.fpmislata.persistencia.hibernate.util; 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; public class GenericIntegratorImpl implements Integrator { @Override public void integrate(Configuration c, SessionFactoryImplementor sfi, SessionFactoryServiceRegistry sfsr) { final EventListenerRegistry eventListenerRegistry = sfsr.getService(EventListenerRegistry.class); prependListeners(eventListenerRegistry); } @Override public void integrate(MetadataImplementor mi, SessionFactoryImplementor sfi, SessionFactoryServiceRegistry sfsr) { final EventListenerRegistry eventListenerRegistry = sfsr.getService(EventListenerRegistry.class); prependListeners(eventListenerRegistry); } @Override public void disintegrate(SessionFactoryImplementor sfi, SessionFactoryServiceRegistry sfsr) { } private void prependListeners(EventListenerRegistry eventListenerRegistry) { eventListenerRegistry.prependListeners(EventType.PRE_INSERT, new GenericEventListenerImpl()); eventListenerRegistry.prependListeners(EventType.PRE_LOAD, new GenericEventListenerImpl()); eventListenerRegistry.prependListeners(EventType.PRE_UPDATE, new GenericEventListenerImpl()); eventListenerRegistry.prependListeners(EventType.PRE_DELETE, new GenericEventListenerImpl()); eventListenerRegistry.prependListeners(EventType.POST_INSERT, new GenericEventListenerImpl()); eventListenerRegistry.prependListeners(EventType.POST_LOAD, new GenericEventListenerImpl()); eventListenerRegistry.prependListeners(EventType.POST_UPDATE, new GenericEventListenerImpl()); eventListenerRegistry.prependListeners(EventType.POST_DELETE, new GenericEventListenerImpl()); } }
Esta clase que implementa el interfaz Integrator simplemente registra cada uno de los listeners que vamos a usar mediante la llamada al método org.hibernate.event.service.spi.EventListenerRegistry.appendListeners(org.hibernate.event.spi.EventType, T…/).html">appendListeners(EventType
package com.fpmislata.persistencia.hibernate.util; import org.hibernate.event.spi.*; public class GenericEventListenerImpl implements PreInsertEventListener,PreLoadEventListener,PreUpdateEventListener,PreDeleteEventListener,PostInsertEventListener,PostLoadEventListener,PostUpdateEventListener,PostDeleteEventListener { @Override public boolean onPreInsert(PreInsertEvent pie) { Object entity=pie.getEntity(); if (entity instanceof PreInsertEventListener) { return ((PreInsertEventListener)entity).onPreInsert(pie); } else { return false; } } @Override public void onPreLoad(PreLoadEvent ple) { Object entity=ple.getEntity(); if (entity instanceof PreLoadEventListener) { ((PreLoadEventListener)entity).onPreLoad(ple); } } @Override public boolean onPreUpdate(PreUpdateEvent pue) { Object entity=pue.getEntity(); if (entity instanceof PreUpdateEventListener) { return ((PreUpdateEventListener)entity).onPreUpdate(pue); } else { return false; } } @Override public boolean onPreDelete(PreDeleteEvent pde) { Object entity=pde.getEntity(); if (entity instanceof PreDeleteEventListener) { return ((PreDeleteEventListener)entity).onPreDelete(pde); } else { return false; } } @Override public void onPostInsert(PostInsertEvent pie) { Object entity=pie.getEntity(); if (entity instanceof PostInsertEventListener) { ((PostInsertEventListener)entity).onPostInsert(pie); } } @Override public void onPostLoad(PostLoadEvent ple) { Object entity=ple.getEntity(); if (entity instanceof PostLoadEventListener) { ((PostLoadEventListener)entity).onPostLoad(ple); } } @Override public void onPostUpdate(PostUpdateEvent pue) { Object entity=pue.getEntity(); if (entity instanceof PostUpdateEventListener) { ((PostUpdateEventListener)entity).onPostUpdate(pue); } } @Override public void onPostDelete(PostDeleteEvent pde) { Object entity=pde.getEntity(); if (entity instanceof PostDeleteEventListener) { ((PostDeleteEventListener)entity).onPostDelete(pde); } } }
Esta clase es un listener de cada uno de los 8 tipos que hemos explicado. Realiza la tarea de llamar al listener correspondiente de la entidad que ha provocado la llamada al listener.
Ahora ya podemos usar los listeners de Hibernate de una forma sencilla. Si queremos que una entidad ejecute un método en un evento concreto, no hay más que implementar el interfaz correspondiente y dicho método se ejecutará automáticamente. En la tabla del principio se han listado los posibles eventos y el interfaz que es necesario implementar.
Veamos un ejemplo para aclarar el funcionamiento:
Queremos que en la entidad Usuario
se ejecute un método en el evento PreInsert en ese caso dicha clase deberá implementar el interfaz correspondiente que en nuestro caso es PreInsertEventListener. La implementación del interfaz PreInsertEventListener conlleva añadir el método onPreInsert(PreInsertEvent event). Este método será el que se ejecute antes de la inserción de la entidad en la que se ha definido.
Veamos el código Java para explicar el proceso:
public class Usuario implements Serializable,PreInsertEventListener { private int idUsuario; private String login; private String nombre; private String ape1; private String ape2; private String password; private String confirmPassword; private Date fechaCreacion; public Usuario() { } public Usuario(String login, String nombre, String ape1, String ape2, String password, String confirmPassword) { this.login = login; this.nombre = nombre; this.ape1 = ape1; this.ape2 = ape2; this.password = password; this.confirmPassword = confirmPassword; } @Override public boolean onPreInsert(PreInsertEvent pie) { } }
Usuario
implementa el interfaz PreInsertEventListener.Vemos que el proceso es muy sencillo, simplemente hay que implementar el interfaz correspondiente al evento que queremos tratar.Ya no es necesario ningún tipo de configuración al respecto.
GenericEventListenerImpl
para que haga uso de ellas. De esa forma nos abstraeriamos de Hibernate y de cualquier otro framework de persistencia.
GenericEventListenerImpl
para que llamara a otras clases.
Ya tenemos definido el método que se ejecutará según el evento que hemos establecido. Ahora veamos cómo se utiliza. En el caso del evento PreInsert al método se le pasa como argumento un objeto de la clase PreInsertEvent. Hay una clase distinta para cada uno de los eventos.
Expliquemos ahora los métodos más importantes de la clase PreInsertEvent
Object getEntity()
:Nos retorna el propio objeto que estamos persistiendo.Sobre este objeto deberemos modificar los datos.
Object[] getState()
:Nos retorna un array con los datos de la entidad. Este array es importantísimo ya que sobre él deberemos también hacer cambios en los datos y se reflejarán al persistirse. El problema es que es un array de datos, con lo que necesitamos el índice de cada propiedad.
String[] getPersister().getPropertyNames()
:Esta array nos va a ayudar a saber el índice de una propiedad en función de su nombre. Para ello necesitaremos la siguiente función:
/** * Obtiene el índice de una propiedad en función de su nombre * @param propertyNames Array con el nombre de las propiedades de una entidad * @param propertyName Nombre de la entidad de la que queremos obtener su índice * @return El indice de la propiedad o -1 si no existe la propiedad. */ private int getPropertyNameIndex(String[] propertyNames,String propertyName) { for(int i=0;i<propertyNames.length;i++) { if (propertyNames[i].equals(propertyName)) { return i; } } return -1; }
Ahora que tenemos la función getPropertyNameIndex(String[] propertyNames,String propertyName)
preparada veamos cómo usar el método onPreInsert(PreInsertEvent event) para ello vamos a seguir con el ejemplo de la clase Usuario
y vamos a establecer la fecha de creación del usuario de forma automática.
public class Usuario implements Serializable, PreInsertEventListener { private int idUsuario; private String login; private String nombre; private String ape1; private String ape2; private String password; private String confirmPassword; private Date fechaCreacion; public Usuario() { } public Usuario(String login, String nombre, String ape1, String ape2, String password, String confirmPassword) { this.login = login; this.nombre = nombre; this.ape1 = ape1; this.ape2 = ape2; this.password = password; this.confirmPassword = confirmPassword; } @Override public boolean onPreInsert(PreInsertEvent pie) { int propertyNameIndex = getPropertyNameIndex(pie.getPersister().getPropertyNames(), "fechaCreacion"); Date fechaCreacion=new Date(); pie.getState()[propertyNameIndex] = fechaCreacion; ((Usuario)(pie.getEntity())).setFechaCreacion(fechaCreacion); return false; } /** * Obtiene el índice de una propiedad en función de su nombre * @param propertyNames Array con el nombre de las propiedades de una entidad * @param propertyName Nombre de la entidad de la que queremos obtener su índice * @return El indice de la propiedad o -1 si no existe la propiedad. */ private int getPropertyNameIndex(String[] propertyNames, String propertyName) { for (int i = 0; i < propertyNames.length; i++) { if (propertyNames[i].equals(propertyName)) { return i; } } return -1; } }
getPropertyNameIndex
que hemos creado para ayudarnos.fechaCreacion
false
ya que si no se cancelará la operación de inserción.getEntity()
) como en el array ( getState()
). La mejor explicación que he encontrado de ésto ,la puedes ver en Hibernate : Pre Database Opertaion Event Listeners
false
para que no se cancele la operación. No es recomendable retornar true
para hacer que no se inserte la entidad ya que no se muestra ningún mensaje al usuario. En ese caso es mejor lanzar una excepción.
Las reglas de negocio es lo más útil que podemos hacer con los listeners de Hibernate.Veamos los tipos de reglas que podemos implementar:
getState()[propertyNameIndex] =newValue
. Ejemplo de cálculos típicos son:Las reglas de negocio son una parte muy importante de una aplicación.En aplicaciones empresariales es realmente fundamental ya que pueden emplear la mayor parte de los recursos al desarrollar la aplicación. También puede ser la parte más cambiante ya que las reglas en una empresa son una parte muy dinámica de la misma.
Sin embargo en mi opinión a las reglas de negocio no se les da la debida importancia.A continuación encontraréis una serie de páginas donde tratan el tema de las reglas de negocio.