Herramientas de usuario

Herramientas del sitio


unidades:06_objetos_validaciones:04_listeners

Diferencias

Muestra las diferencias entre dos versiones de la página.


unidades:06_objetos_validaciones:04_listeners [2023/04/07 21:26] (actual) – creado - editor externo 127.0.0.1
Línea 1: Línea 1:
 +====== 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 | <javadoc h41>org.hibernate.event.spi.PreInsertEventListener|PreInsertEventListener</javadoc> | Se ejecuta antes de la inserción de la entidad |
 +| PreLoad | <javadoc h41>org.hibernate.event.spi.PreLoadEventListener|PreLoadEventListener</javadoc> | Se ejecuta antes de la carga de la entidad |
 +| PreUpdate | <javadoc h41>org.hibernate.event.spi.PreUpdateEventListener|PreUpdateEventListener</javadoc> | Se ejecuta antes de la actualización de la entidad |
 +| PreDelete | <javadoc h41>org.hibernate.event.spi.PreDeleteEventListener|PreDeleteEventListener</javadoc> | Se ejecuta antes del borrado de la entidad |
 +| PostInsert | <javadoc h41>org.hibernate.event.spi.PostInsertEventListener|PostInsertEventListener</javadoc> | Se ejecuta tras la inserción de la entidad |
 +| PostLoad | <javadoc h41>org.hibernate.event.spi.PostLoadEventListener|PostLoadEventListener</javadoc> | Se ejecuta tras la carga de la entidad |
 +| PostUpdate | <javadoc h41>org.hibernate.event.spi.PostUpdateEventListener|PostUpdateEventListener</javadoc> | Se ejecuta tras la actualización de la entidad |
 +| PostDelete | <javadoc h41>org.hibernate.event.spi.PostDeleteEventListener|PostDeleteEventListener</javadoc> | 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 <javadoc h41>org.hibernate.integrator.spi.Integrator</javadoc>. También hay que indicar a hibernate la clase que implementa <javadoc h41>org.hibernate.integrator.spi.Integrator|Integrator</javadoc>.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:
 +  - Crear el paquete ''com.fpmislata.persistencia.hibernate.util''
 +  - En el paquete ''com.fpmislata.persistencia.hibernate.util'' crear la clase ''GenericEventListenerImpl''
 +  - En el paquete ''com.fpmislata.persistencia.hibernate.util'' crear la clase ''GenericIntegratorImpl''
 +  - Crear la carpeta ''META-INF'' en el directorio ''src''.
 +  - Dentro de la carpeta ''META-INF'' crea la carpeta ''services''
 +  - En la carpeta ''META-INF/services'' crear un fichero con el nombre ''org.hibernate.integrator.spi.Integrator''
 +  - 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:
 +<code java | 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());
 +    }
 +}
 +</code>
 +
 +Esta clase que implementa el interfaz <javadoc h41>org.hibernate.integrator.spi.Integrator|Integrator</javadoc> simplemente registra cada uno de los listeners que vamos a usar mediante la llamada al método <javadoc h41>org.hibernate.event.service.spi.EventListenerRegistry#appendListeners(org.hibernate.event.spi.EventType, T...)|appendListeners(EventType<T> type,T... listeners)</javadoc>
 +
 +<code java | 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);
 +        } 
 +    }
 +    
 +}
 +</code>
 +
 +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 <javadoc h41>org.hibernate.event.spi.PreInsertEventListener|PreInsertEventListener</javadoc>. La implementación del interfaz <javadoc h41>org.hibernate.event.spi.PreInsertEventListener|PreInsertEventListener</javadoc> conlleva añadir el método <javadoc h41>org.hibernate.event.spi.PreInsertEventListener#onPreInsert(org.hibernate.event.spi.PreInsertEvent)|onPreInsert(PreInsertEvent event)</javadoc>. 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:
 +<code java 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) {       
 +                
 +    }   
 +}
 +</code>
 +
 +  * En la línea 1 podemos apreciar cómo la clase ''Usuario'' implementa el interfaz <javadoc h41>org.hibernate.event.spi.PreInsertEventListener|PreInsertEventListener</javadoc>.
 +  * En las líneas 24 a 27 se ve el método <javadoc h41>org.hibernate.event.spi.PreInsertEventListener#onPreInsert(org.hibernate.event.spi.PreInsertEvent)|onPreInsert(PreInsertEvent event)</javadoc> que se ejecutará antes de hacer la inserción.
 +
 +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.
 +
 +<note important>
 +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.
 +</note>
 +
 +<note tip>
 +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.
 +</note>
 +
 +<note tip>
 +En JPA existen anotaciones como <javadoc jee6>javax.persistence.PrePersist</javadoc> que realizan tareas similares. No se han explicado ya que desde hibernate usando el objeto <javadoc h41>org.hibernate.Session|Session</javadoc> no funcionan, siendo necesario usar el API específica de JPA que implica usar <javadoc jee6>javax.persistence.EntityManager</javadoc> en vez de <javadoc h41>org.hibernate.Session|Session</javadoc>
 +</note>
 +===== 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 <javadoc h41>org.hibernate.event.spi.PreInsertEvent|PreInsertEvent</javadoc>. Hay una clase distinta para cada uno de los eventos.
 +
 +Expliquemos ahora los métodos más importantes de la clase <javadoc h41>org.hibernate.event.spi.PreInsertEvent|PreInsertEvent</javadoc>
 +
 +  * ''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:
 +<code java>
 +/**
 +  * 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;
 +}
 +</code>
 +
 +Ahora que tenemos la función ''getPropertyNameIndex(String[] propertyNames,String propertyName)'' preparada veamos cómo usar el método <javadoc h41>org.hibernate.event.spi.PreInsertEventListener#onPreInsert(org.hibernate.event.spi.PreInsertEvent)|onPreInsert(PreInsertEvent event)</javadoc> 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.
 +
 +<code java 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;
 +    }
 +}
 +</code>
 +
 +  * 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.
 +
 +<note tip>
 +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 [[http://anshuiitk.blogspot.com.es/2010/11/hibernate-pre-database-opertaion-event.html|Hibernate : Pre Database Opertaion Event Listeners]]
 +</note>
 +
 +<note warning>
 +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.
 +</note>
 +===== 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.
 +  * [[http://www.automatedbusinesslogic.com/articles/business-logic-architecture|Business Logic Architecture]]: Explicación de cómo debería ser una arquitectura para la creación de reglas de negocio.
 +  * [[https://community.jboss.org/wiki/BusinessLogicForHibernateJPA|Business Logic for Hibernate / JPA]]: Otro artículo sobre reglas de negocio en Hibernate y JPA.
 +  * [[http://www.jboss.org/drools/|Drools - The Business Logic integration Platform]]: Drools es un motor de reglas de negocio.Permite escribir las reglas de nuestras entidades en repositorios de reglas para luego ser llamadas desde nuestra aplicación. Es un proyecto muy importante y complejo aunque poco conocido.
 +  * [[http://www.automatedbusinesslogic.com|Automated Business Logic.]]: Framework para facilitar la implementación de reglas de negocio.
 +  * [[http://wrschneider.blogspot.com.es/2005/01/avoiding-anemic-domain-models-with.html]]:Avoiding Anemic Domain Models with Hibernate
 +
 +===== Referencias =====
 +
 +  * [[http://planet.jboss.org/post/event_listener_registration|Event Listener Registration]]:Post sobre cómo añadir listeners en Hibernate 4 ya que se ha modificado en esta versión.
 +  * [[http://stackoverflow.com/questions/7753702/jpa-hibernate-preupdate-doesnt-update-parent-object|JPA/Hibernate preUpdate doesn't update parent object]]: Limitaciones en el uso de operaciones en los listeners.
 +  * [[http://anshuiitk.blogspot.com.es/2010/11/hibernate-pre-database-opertaion-event.html|Hibernate : Pre Database Operation Event Listeners]]