Auditoría de entidades con Hibernate Envers y Spring Data JPA.

En este tutorial veremos cómo configurar un sistema de auditoría de cambios en las entidades de nuestra capa de persistencia con el soporte de Hibernate Envers y Spring Data JPA.

Auditoría de entidades con Hibernate Envers y Spring Data JPA.


0. Índice de contenidos.


1. Introducción

Antes o después, en cualquier aplicación de gestión empresarial, se plantea la necesidad de mantener un histórico de quién ha hecho qué, cuándo y desde dónde con la información que se mantiene en nuestro sistema. No solo por aspectos legales, sino de simple control de cambios, en algún momento nos pedirán mantener una auditoría de cambios sobre esa información.

A nivel técnico podemos implementarlo de muchas maneras:

  • a bajo nivel directamente en la base de datos con triggers,
  • enganchándonos con los eventos del ORM que nos de soporte a persistencia,
  • con el soporte de AOP si usamos algún framework que nos de soporte para ello,
  • ensuciando el código con consultas innecesarias, consumo de memoria y objetos en sesión para realizar comparaciones y la persistencia a mano,…

Entendiendo que la última opción es inadmisible, en este tutorial vamos a ver cómo usando Hibernate o el soporte de éste para JPA, podemos implementar un control de cambios de una manera muy limpia y poco intrusiva en nuestro código, con la extensión Hibernate Envers.

Para enriquecer un poco más el tutorial además harenos uso de la librería Spring Data Envers, que extiende los repositorios de Spring Data para permitir, de una manera sencilla, el acceso al histórico de modificaciones de una entidad.

2. Entorno.

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15′ (2.5 GHz Intel Core i7, 16GB DDR3).
  • Sistema Operativo: Mac OS El Capitan 10.11
  • Hibernate 4.3.11.Final
  • Spring Data 1.10.2.RELEASE
  • Spring Data Envers 1.0.2.RELEASE

3. Estrategia de versionado.

Hibernate Envers extiende el soporte por defecto del ORM para engancharse en el ciclo de vida de persistencia de una entidad; sabiendo que una entidad está marcada como detached y los cambios realizados sobre la misma, puesto que tiene que realizar un update de esos cambios, “aprovecha” para almacenar la información de los mismos en una tabla de auditoría.

Esa estrategia pasa por mantener una tabla espejo de la original añadiendo dos columnas adicionales por defecto:

  • REV: identificador de la revisión
  • REVTYPE: tipo de revisión (1 inserción, 2 modificación o 3 borrado)

El identificador de la revisión mantiene una clave foránea con una tabla con el histórico de información de todas las revisiones que podemos enriquecer con la información que estimemos necesaria: timestamp, user_name, remote_address,…

hibernate-envers-spring-data-01

Si estamos usando el soporte de Hibernate para generar el modelo en nuestro entorno de tests, para crear las tablas automáticamente solo debemos hacer uso de la propiedad: <property name=”hibernate.hbm2ddl.auto” value=”update” />


4. Configuración.

Lo primero, como no podía ser de otra forma es añadir las dependencias de las librerías que vamos a necesitar en nuestro pom.xml:

Es importante la correlación de versiones, una versión inferior de la librería spring-data-jpa hará que los repositorios no funcionen correctamente.

Desde el punto de vista estríctamente de Hibernate Envers lo único que debemos hacer es configurar, si lo estimamos necesario, las propiedades que nos permiten renombrar las tablas o sufijos de las mismas donde se realizará la auditoría. Así, en la declaración de la factoría de entityManagers para JPA.

Desde el punto de vista de los repositorios de Spring Data debemos añadir la siguiente configuración:

Cuando se generen las implementaciones de los repositorios, la factoría EnversRevisionRepositoryFactoryBean añadirá las implementación de las operaciones necesarias para recuperar el listado de revisiones de una entidad y permitirá el acceso al detalle de una revisión concreta.


5. Uso.

Para enriquecer la tabla de auditoría debemos primero mapearla añadiendo una entidad como la que sigue, con las propiedades que estimemos necesarias para almacenar:

Con la anotación @RevisionNumber marcamos la propiedad con la clave única de la revisión y con la anotación @RevisionTimestamp la propiedad que almacenará la fecha de modificación.

Con la anotación @RevisionEntity haremos referencia a un listener que se ejecutará previo a las operaciones de auditoría que será el que realmente dote de contenido dicha información.

Como se puede ver a continuación podemos añadir información sobre el usuario obteniéndolo del contexto.

Lo interesante es que este listener es capaz de engancharse con Spring Security para recuperar información del usuario conectado, así como de la ip remota de acceso.

En este punto ya podemos añadir la configuración necesaria a nuestras entidades para auditarlas, bastaría la anotación @Audited, como se puede ver a continuación:

A más podemos indicar que solo audite las relaciones de la entidad, no las entidades relacionadas con targetAuditMode = RelationTargetAuditMode.NOT_AUDITED.

También podemos renombrar la estrategia por defecto de nombres indicando con la anotación @AuditTable el nombre y esquema de la tabla de auditoría.

Por último, para habilitar el acceso a las operaciones de recuperación de información en el repositorio de Spring Data basta con extender de la interfaz RevisionRepository.

Esta interfaz añade operaciones como las siguientes:

hibernate-envers-spring-data-02

6. Tests.

Como no podía ser de otra forma comprobaremos que todo funciona correctamente con un test de integración que validará que la información de las revisiones se almacena correctamente.

Podría tener un código como el siguiente:

Tres cuestiones a destacar:

  • con la operación save se almacenará no solo la información de la entidad sino de la revisión, pero al enganchar con el ciclo de vida de las operaciones de persistencia solo será efectiva cuando se haga un flush o se fuerce un commit.
  • en el entorno de tests con el soprote de Spring podemos hacer uso de las operaciones de la clase de utilidades TestTransaction para forzar la apertura y cierre de transacciones.
  • en los repositorios de Spring Data habilitados como hemos visto ahora tendemos operaciones del tipo repository.findRevisions(…) para recuperar la información del histórico de revisiones.


7. Referencias.


8. Conclusiones.

Ahora solo nos faltaría programar un proceso de historificación de los datos de las tablas de auditoría… ups!

Un saludo.

Jose