Herramientas de usuario

Herramientas del sitio


unidades:06_objetos_validaciones:04_listeners

Listeners y reglas de negocio

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

Configuración

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:

  1. Crear el paquete com.fpmislata.persistencia.hibernate.util
  2. En el paquete com.fpmislata.persistencia.hibernate.util crear la clase GenericEventListenerImpl
  3. En el paquete com.fpmislata.persistencia.hibernate.util crear la clase GenericIntegratorImpl
  4. Crear la carpeta META-INF en el directorio src.
  5. Dentro de la carpeta META-INF crea la carpeta services
  6. En la carpeta META-INF/services crear un fichero con el nombre org.hibernate.integrator.spi.Integrator
  7. Añadir al fichero 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:

| GenericIntegratorImpl.java
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 type,T…/ listeners).html"> listeners)

| GenericEventListenerImpl.java
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.

Definiendo Listeners

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:

1
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) {       
 
    }   
}

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.

El problema de usar listeners en nuestras entidades es que ahora nuestro código ya está atado a Hibernate, ya ahora implementa un interfaz que sólo existe en Hibernate. Una solución bastante sencilla sería crearte tus propios interfaces o anotaciones similares a los tipos de eventos y modificar la clase GenericEventListenerImpl para que haga uso de ellas. De esa forma nos abstraeriamos de Hibernate y de cualquier otro framework de persistencia.
Se podría argumentar que el código de los listeners no debería estar en la misma clase que las entidades , en ese caso, sería muy sencillo cambiar el código de GenericEventListenerImpl para que llamara a otras clases.
En JPA existen anotaciones como javax.persistence.PrePersist que realizan tareas similares. No se han explicado ya que desde hibernate usando el objeto Session no funcionan, siendo necesario usar el API específica de JPA que implica usar javax.persistence.EntityManager en vez de Session

Usando Listeners

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.

1 | Uso de onPreInsert
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;
    }
}
  • De las líneas 34 a 48 está la nueva función getPropertyNameIndex que hemos creado para ayudarnos.
  • En la línea 26 se obtiene el índice de la propiedad fechaCreacion
  • En la línea 27 creamos la fecha que queremos establecer en el objeto.
  • En la línea 28 modificamos el array que contiene los datos de la entidad estableciendo la fecha.
  • En la línea 29 modificamos el objeto estableciendo la fecha.
  • En la línea 31 se retorna false ya que si no se cancelará la operación de inserción.
Recuerda que se necesitan cambiar los datos tanto en el objeto ( 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
Recuerda siempre retornar 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.

Reglas de negocio

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:

  • Restricciones: Son las típicas reglas de validación para comprobar si todos los valores de la entidad son correctos. En caso de que algún valor no sea correcto deberemos lanzar una excepción para indicarlo.
  • Acciones: Son otras tareas a realizar cuando se persiste el objeto. Ejemplo de típicas acciones son:
    • Enviar un correo electrónico de bienvenida cuando se da de alta usuario
    • Hacer un cargo en la tarjeta de crédito al comprar un producto.
    • Enviar un SMS para confirmar un pago.
    • Etc.
  • Cálculos derivados: Es calcular el valor de otras propiedades ya que sus valores dependen de otros.Como ya hemos explicado para cambiar los valores deberemos hacer uso de getState()[propertyNameIndex] =newValue. Ejemplo de cálculos típicos son:
    • Calcular el importe total de una compra sumando los importes de los productos.
    • Añadir la fecha de creación de un usuario automáticamente.
    • Calcular el importe de una línea de factura en función del número de elementos comprados y su precio.
    • Etc.

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.

Referencias

unidades/06_objetos_validaciones/04_listeners.txt · Última modificación: 2023/04/07 21:26 por 127.0.0.1