Hibernate – Interceptores

Los interceptores se utilizan junto con las clases administradas de Java EE para permitir que los desarrolladores invoquen métodos de interceptor en una clase de destino asociada, junto con invocaciones de métodos o eventos de ciclo de vida. Los usos comunes de los interceptores son el registro, la auditoría y la creación de perfiles.

La especificación Interceptors 1.1 es parte de la versión final de JSR 318, Enterprise JavaBeans 3.1, disponible en http://jcp.org/en/jsr/detail?id=318.

Un interceptor se puede definir dentro de una clase de destino como un método de interceptor, o en una clase asociada denominada clase de interceptor. Las clases de interceptor contienen métodos que se invocan junto con los métodos o eventos del ciclo de vida de la clase de destino.

Clases de interceptor: las clases de interceptor se pueden designar con la anotación opcional javax.interceptor.Interceptor, pero no es necesario que las clases de interceptor estén anotadas. Una clase de interceptor debe tener un constructor público sin argumentos.

La clase de destino puede tener cualquier número de clases de interceptor asociadas. El orden en que se invocan las clases de interceptor está determinado por el orden en que se definen las clases de interceptor en la anotación javax.interceptor.Interceptors. Sin embargo, este orden se puede anular en el descriptor de implementación.

Las clases de interceptor pueden ser objetivos de inyección de dependencia. La inyección de dependencia se produce cuando se crea la instancia de la clase de interceptor, utilizando el contexto de nomenclatura de la clase de destino asociada, y antes de que se invoquen las devoluciones de llamada de @PostConstruct.

Ciclo de vida del interceptor : las clases de interceptor tienen el mismo ciclo de vida que su clase de destino asociada. Cuando se crea una instancia de clase de destino, también se crea una instancia de clase de interceptor para cada clase de interceptor declarada en la clase de destino. Es decir, si la clase de destino declara varias clases de interceptor, se crea una instancia de cada clase cuando se crea la instancia de la clase de destino. La instancia de la clase de destino y todas las instancias de la clase de interceptor se instancian por completo antes de que se invoquen las devoluciones de llamada de @PostConstruct, y las devoluciones de llamada de @PreDestroy se invocan antes de que se destruyan las instancias de la clase de destino y la clase de interceptor.

Interceptores y CDI: contextos e inyección de dependencias para la plataforma Java EE (CDI) se basan en la funcionalidad básica de los interceptores Java EE. Para obtener información sobre los interceptores CDI, incluido un análisis de los tipos de enlaces de interceptores, consulte Uso de interceptores en aplicaciones CDI. 

Aquí veremos varias formas de interceptar operaciones dentro de la implementación de mapeo relacional abstracto de Hibernate.

Interceptores de hibernación 

El Hibernate Interceptor es una interfaz que nos permite reaccionar ante ciertos eventos dentro de Hibernate. Estos interceptores se registran como devoluciones de llamada y proporcionan enlaces de comunicación entre la sesión y la aplicación de Hibernate. Con tal devolución de llamada, una aplicación puede interceptar las operaciones centrales de Hibernate, como guardar, actualizar, eliminar, etc.

Tipos de interceptores

Hay dos formas de definir los interceptores:

  1. implementando la interfaz org.hibernate.Interceptor
  2. extendiendo la clase org.hibernate.EmptyInterceptor

2.1: Implementación de una interfaz de interceptor: La implementación de org.hibernate.Interceptor requiere la implementación de unos 14 métodos complementarios. Estos métodos incluyen onLoad, onSave, onDelete, findDirty y algunos más. También es importante asegurarse de que cualquier clase que implemente la interfaz Interceptor sea serializable (implementa java.io.Serializable).

Un ejemplo típico sería el siguiente:

// Class 
public class CustomInterceptorImpl
implements Interceptor, Serializable {

    // Annotation 
    @Override 
    // Method 
    public boolean onLoad(Object entity, Serializable id, Object[] state,
                          String[] propertyNames, Type[] types) 
            throws CallbackException {
            
               // ... return false; }
               // ... @Override public String onPrepareStatement(String sql)
               {
                 // ... return sql; }
               }

Si no hay requisitos especiales, se recomienda ampliar la clase EmptyInterceptor y anular solo los métodos necesarios.

2.2: Ampliación de EmptyInterceptor: la ampliación de la clase org.hibernate.EmptyInterceptor proporciona una forma más sencilla de definir un interceptor. Ahora solo necesitamos anular los métodos que se relacionan con la operación que queremos interceptar.

Por ejemplo, podemos definir nuestro CustomInterceptor como:

public class CustomInterceptor extends EmptyInterceptor { }

Y si necesitamos interceptar las operaciones de guardado de datos antes de que se ejecuten, debemos anular el método onSave:

// Annotation 
@Override
// Method 
public boolean onSave(Object entity, Serializable id, Object[] state,
                      String[] propertyNames, Type[] types) {

    if (entity instanceof User) {

        logger.info(((User) entity).toString());
    }
    
    return super.onSave(entity, id, state, propertyNames, types);
}

Observe cómo esta implementación simplemente imprime la entidad, si es un usuario. Si bien es posible devolver un valor de verdadero o falso, es una buena práctica permitir la propagación del evento onSave invocando super.onSave(). Otro caso de uso sería proporcionar una pista de auditoría para las interacciones de la base de datos. Podemos usar el método onFlushDirty() para saber cuándo cambia una entidad.

Para el objeto Usuario, podemos decidir actualizar su propiedad de fecha lastModified siempre que ocurran cambios en las entidades de tipo Usuario.

En el siguiente método, ilustraremos cómo se puede lograr esto, que es el siguiente:

// Anotación

@Anular

// Método

booleano público onFlushDirty (entidad de objeto, ID serializable,

                            Objeto[] estado actual, Objeto [] estado anterior,

                            String[] propertyNames, Type[] tipos) {

    if (entidad instancia de usuario) {

        (entidad (usuario)).setLastModified(nueva fecha());

        logger.info(((Usuario) entidad).toString());

    }

    return super.onFlushDirty(entidad, id, estado actual, estado anterior, nombres de propiedad, tipos);

}

Otros eventos como eliminar y cargar (inicialización de objetos) pueden interceptarse implementando los métodos onDelete y onLoad correspondientes respectivamente.

Registro de interceptores

Un interceptor de Hibernate puede registrarse como de ámbito de sesión o de ámbito de fábrica de sesión.

3.1: Interceptor con ámbito de sesión: un interceptor con ámbito de sesión está vinculado a una sesión específica. Se crea cuando la sesión se define o se abre como:

sesión estática pública getSessionWithInterceptor (interceptor interceptor)

lanza IOException {

    devuelve getSessionFactory().withOptions() .interceptor(interceptor).openSession();

}

En lo anterior, registramos explícitamente un interceptor con una sesión de hibernación particular.

3.2: Interceptor con ámbito de SessionFactory: un interceptor con ámbito de SessionFactory se registra antes de construir una SessionFactory. Esto normalmente se hace a través del método applyInterceptor en una instancia de SessionFactoryBuilder:

ServiceRegistry serviceRegistry = configureServiceRegistry(); 

SessionFactory sessionFactory = getSessionFactoryBuilder(serviceRegistry).applyInterceptor(new CustomInterceptor()) .build();

Es importante tener en cuenta que se aplicará un interceptor con ámbito de SessionFactory a todas las sesiones. Por lo tanto, debemos tener cuidado de no almacenar estados específicos de la sesión, ya que este interceptor será utilizado por diferentes sesiones al mismo tiempo.

  • Para un comportamiento específico de la sesión, se recomienda abrir explícitamente una sesión con un interceptor diferente, como se mostró anteriormente.
  • Para los interceptores con alcance de SessionFactory, naturalmente debemos asegurarnos de que sea seguro para subprocesos. Esto se puede lograr especificando un contexto de sesión en el archivo de propiedades:

hibernate.current_session_context_class=org.hibernate.context.internal.ThreadLocalSessionContext

O agregando esto a nuestro archivo de configuración XML:

<nombre de propiedad=”hibernate.current_session_context_class”> 

org.hibernate.context.internal.ThreadLocalSessionContext 

</propiedad>

Además, para garantizar la capacidad de serialización, los interceptores con alcance de SessionFactory deben implementar el método readResolve de la interfaz Serializable.

Conclusión: hemos visto cómo definir y registrar los interceptores de Hibernate, ya sea con ámbito de sesión o con ámbito de SessionFactory. En cualquier caso, debemos asegurarnos de que los interceptores sean serializables, especialmente si queremos una sesión serializable.

Otras alternativas a los interceptores incluyen Hibernate Events y JPA Callbacks. Y, como siempre, puede consultar el código fuente completo en Github.

Publicación traducida automáticamente

Artículo escrito por mayurprajapati0369 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *