Interceptores de hibernación

1. Información general

En esta discusión, veremos varias formas de interceptar operaciones dentro de la implementación de mapeo relacional abstracto de Hibernate.

2. Definición de interceptores de hibernación

El Interceptor de Hibernate es una interfaz que nos permite reaccionar a 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.

Hay dos formas de definir 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 se vería así:

public class CustomInterceptorImpl implements Interceptor, Serializable { @Override 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 encarecidamente extender la clase EmptyInterceptor y solo anular los métodos necesarios.

2.2. Ampliación de EmptyInterceptor

La extensió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 :

@Override 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 práctica buena para permitir la propagación de OnSave caso 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 entidades de tipo Usuario .

Esto se puede lograr con:

@Override public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object [] previousState, String[] propertyNames, Type[] types) { if (entity instanceof User) { ((User) entity).setLastModified(new Date()); logger.info(((User) entity).toString()); } return super.onFlushDirty(entity, id, currentState, previousState, propertyNames, types); }

Otros eventos como delete y load (inicialización del objeto) se pueden interceptar implementando los métodos onDelete y onLoad correspondientes respectivamente.

3. Registro de interceptores

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

3.1. Interceptor de alcance de sesión

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

public static Session getSessionWithInterceptor(Interceptor interceptor) throws IOException { return 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 alcance de SessionFactory

Un interceptor con ámbito de SessionFactory se registra antes de construir una SessionFactory. Esto generalmente 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 el estado específico 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 ámbito 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:

 org.hibernate.context.internal.ThreadLocalSessionContext 

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

4. Conclusión

Hemos visto cómo definir y registrar interceptores de Hibernate, ya sea con alcance de sesión o alcance 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.