Interceptores y Eventos en Hibernate

Es útil para la aplicación reaccionar a ciertos eventos que ocurren dentro de Hibernate. Esto permite la implementación de funcionalidades genéricas y la extensión de la funcionalidad de Hibernate.

La interfaz Interceptor brinda callbacks desde la sesión a la aplicación, permitiendole a ésta última inspeccionar y/o manipular las propiedades de un objeto persistente antes de que sea guardado, actualizado, borrado o cargado. Un uso posible de esto es seguir la pista de la información de auditoría. Por ejemplo, el siguiente Interceptor establece automáticamente el createTimestamp cuando se crea unAuditable y se actualiza la propiedad lastUpdateTimestamp cuando se actualiza un Auditable.
Puede implementar el Interceptor directamente o extender el EmptyInterceptor.

package org.hibernate.test;

import java.io.Serializable;
import java.util.Date;
import java.util.Iterator;

import org.hibernate.EmptyInterceptor;
import org.hibernate.Transaction;
import org.hibernate.type.Type;

public class AuditInterceptor extends EmptyInterceptor {

    private int updates;
    private int creates;
    private int loads;

    public void onDelete(Object entity,
                         Serializable id,
                         Object[] state,
                         String[] propertyNames,
                         Type[] types) {
        // do nothing
    }

    public boolean onFlushDirty(Object entity,
                                Serializable id,
                                Object[] currentState,
                                Object[] previousState,
                                String[] propertyNames,
                                Type[] types) {

        if ( entity instanceof Auditable ) {
            updates++;
            for ( int i=0; i < propertyNames.length; i++ ) {
                if ( "lastUpdateTimestamp".equals( propertyNames[i] ) ) {
                    currentState[i] = new Date();
                    return true;
                }
            }
        }
        return false;
    }

    public boolean onLoad(Object entity,
                          Serializable id,
                          Object[] state,
                          String[] propertyNames,
                          Type[] types) {
        if ( entity instanceof Auditable ) {
            loads++;
        }
        return false;
    }

    public boolean onSave(Object entity,
                          Serializable id,
                          Object[] state,
                          String[] propertyNames,
                          Type[] types) {

        if ( entity instanceof Auditable ) {
            creates++;
            for ( int i=0; i<propertyNames.length; i++ ) {
                if ( "createTimestamp".equals( propertyNames[i] ) ) {
                    state[i] = new Date();
                    return true;
                }
            }
        }
        return false;
    }

    public void afterTransactionCompletion(Transaction tx) {
        if ( tx.wasCommitted() ) {
            System.out.println("Creations: " + creates + ", Updates: " + updates, "Loads: " + loads);
        }
        updates=0;
        creates=0;
        loads=0;
    }

}

Hay dos clases de interceptores: incluído en Session- e incluído en SessionFactory.
Se especifica un interceptor incluído Session cuando se abre una sesión utilizando uno de los métodos SessionFactory.openSession() sobrecargados aceptando un Interceptor.

Session session = sf.openSession( new AuditInterceptor() );

Un interceptor incluido en SessionFactory se encuentra registrado con el objeto Configuration antes de construir el SessionFactory. En este caso, el interceptor proveido será aplicado a todas las sesiones abiertas desde ese SessionFactory; a menos de que se abra una sesión especificando explícitamente el interceptor a utilizar. Los interceptores SessionFactory incluidos deben ser a prueba de hilos. Asegúrese de no almacenar un estado especifico a la sesión ya que múltiples sesiones utilizarán este interceptor potencialmente de manera concurrente.

new Configuration().setInterceptor( new AuditInterceptor() );
Si tiene que reaccionar a eventos particulares en su capa de persistencia, también puede utilizar la arquitectura de eventos de Hibernate3. El sistema de eventos se puede ser utilizar además de o como un remplazo para los interceptores.

Todos los métodos de la interfaz Session se correlacionan con un evento. Tiene un LoadEvent, unFlushEvent, etc. Consulte el DTD del archivo de configuración XML o el paquete org.hibernate.event para ver la lista completa de los tipos de eventos definidos. Cuando se realiza una petición de uno de estos métodos, la Session de Hibernate genera un evento apropiado y se lo pasa al escucha (listener) de eventos configurado para ese tipo. Tal como vienen, estos escuchas implementan el mismo procesamiento en aquellos métodos donde siempre resultan . Sin embargo, usted es libre de implementar una personalización de una de las interfaces escuchas (por ejemplo, el LoadEvent es procesado por la implementación registrada de la interfaz LoadEventListener), en cuyo caso su implementación sería responsable de procesar cualquier petición load() realizada a la Session.

Los escuchas se deben considerar como singletons. Esto significa que son compartidos entre las peticiones y por lo tanto, no deben guardar ningún estado como variables de instancia.

Un escucha personalizado implementa la interfaz apropiada para el evento que quiere procesar y/o extender una de las clases base de conveniencia (o incluso los escuchas de eventos predeterminados utilizados por Hibernate de fábrica al declararlos como no-finales para este propósito). Los escuchas personalizados pueden ser registrados programáticamente a través del objeto Configuration, o especificados en el XML de configuración de Hibernate. No se soporta la configuración declarativa a través del archivo de propiedades. Este es un ejemplo de un escucha personalizado de eventos load:

public class MyLoadListener implements LoadEventListener {
    // this is the single method defined by the LoadEventListener interface
    public void onLoad(LoadEvent event, LoadEventListener.LoadType loadType)
            throws HibernateException {
        if ( !MySecurity.isAuthorized( event.getEntityClassName(), event.getEntityId() ) ) {
            throw MySecurityException("Unauthorized access");
        }
    }
}

También necesita una entrada de configuración diciéndole a Hibernate que utilice el oyente en vez del oyente por defecto:


<hibernate-configuration>
    <session-factory>
        ...
        <event type="load">
            <listener class="com.eg.MyLoadListener"/>
            <listener class="org.hibernate.event.def.DefaultLoadEventListener"/>
        </event>
    </session-factory>
</hibernate-configuration
>

En cambio, puede registrarlo programáticamente:

Configuration cfg = new Configuration();
LoadEventListener[] stack = { new MyLoadListener(), new DefaultLoadEventListener() };
cfg.EventListeners().setLoadEventListeners(stack);

Los oyentes registrados declarativamente no pueden compartir instancias. Si se utiliza el mismo nombre de clase en múltiples elementos , cada referencia resultará en una instancia separada de esa clase. Si necesita compartir instancias de oyentes entre tipos de oyentes debe usar el enfoque de registración programática.

¿Por qué implementar una interfaz y definir el tipo específico durante la configuración? Una implementación de escucha podría implementar múltiples interfaces de escucha de eventos. Teniendo el tipo definido adicionalmente durante la registración hace más fácil activar o desactivar escuchas personalizados durante la configuración.

No hay comentarios.: