Contenido del curso
Primeros pasos
Spring Data Repositories
- 7

Qué son los Spring Data Repositories
08:39 min - 8

Guardar y Actualizar Registros con Spring Data Repositories
08:34 min - 9

Eliminar elementos con Spring Data JPA: método deleteById
05:36 min - 10

Lazy vs Eager en relaciones JPA
15:09 min - 11

Query Methods en Spring para Consultas Personalizadas
08:27 min - 12

Filtrar pizzas con Containing y Not en JPA
07:27 min - 13

Fechas y listas en query methods de JPA
11:25 min - 14

findTop y Optional en Spring Data JPA
09:30 min - 15

Paginación y Ordenación con Spring Data Repositories
07:39 min - 16

Ordenamiento Dinámico con Paging and Sorting Repository
07:58 min
Personalización de queries
Características avanzadas
Próximos pasos
Listeners JPA para auditar cambios en entidades
Resumen
Auditar cambios en una entidad va más allá de registrar fechas de creación o modificación. Con un EntityListener personalizado en Spring Data JPA puedes capturar cada movimiento que ocurra sobre tus entidades, comparar estados anteriores con nuevos valores y enviar esa información a logs o bases de datos de auditoría. Esto es clave para desarrolladores backend que necesitan trazabilidad fina sin acoplar lógica de auditoría al servicio.
Cómo organizar el paquete de auditoría en tu proyecto
Antes de escribir el listener, conviene tener una estructura clara para mantener todo lo relacionado con auditoría en un solo lugar.
Dentro de la capa de persistencia, crea un paquete llamado audit. Ahí van a vivir todos los listeners que escribas para tus distintas entidades, además de la clase AuditableEntity que ya usabas para fechas de creación y modificación. Tener este paquete dedicado evita que la lógica de auditoría se mezcle con repositorios o entidades de negocio [01:00].
¿Qué es un EntityListener en JPA? Es una clase que recibe callbacks del ciclo de vida de una entidad (persist, update, remove, load) y permite ejecutar lógica automática cuando ocurren esos eventos.
Qué anotaciones de ciclo de vida usar para auditar
Dentro del paquete audit, crea una clase llamada AuditPizzaListener. Esta clase concentra los métodos que se ejecutarán automáticamente en distintos momentos del ciclo de vida de PizzaEntity.
Las anotaciones que vas a usar son propias de JPA y cada una tiene una regla común: el método debe ser público, sin retorno (void) y recibir el entity como parámetro.
@PostPersist: se ejecuta después de insertar un nuevo registro.@PostUpdate: se ejecuta después de actualizar un registro existente.@PreRemove: se ejecuta antes de eliminar un registro de la base de datos.@PostLoad: se ejecuta después de que unselectcargue información en el entity.
Puedes combinar @PostPersist y @PostUpdate sobre el mismo método si quieres tratar ambos eventos igual. Por ejemplo, un método onPostPersist(PizzaEntity entity) que imprima entity.toString() te muestra en consola cómo quedó la pizza después de guardarse o actualizarse [02:30].
Para que el listener realmente intervenga, debes registrarlo en la entidad con la anotación @EntityListeners({AuditableListener.class, AuditPizzaListener.class}). Como ya tenías un listener previo, ahora pasan a ir entre llaves porque son varios.
Cómo capturar el estado anterior con @PostLoad
Auditar el después está bien, pero lo realmente útil es comparar el antes y el después. Aquí entra @PostLoad.
La idea es declarar un atributo private PizzaEntity currentValue; dentro del listener y, en el método anotado con @PostLoad, guardar ahí una copia del entity recién cargado. El truco está en clonar el objeto, no asignarlo por referencia:
java this.currentValue = SerializationUtils.clone(entity);
SerializationUtils viene de org.springframework.util. Si asignaras directamente this.currentValue = entity, ambos apuntarían a la misma posición en memoria y, cuando JPA actualice el entity, también verías los nuevos valores en currentValue. Perderías la foto del estado anterior.
Para que el clone funcione, PizzaEntity debe implementar la interfaz Serializable además de heredar de AuditableEntity. Sin esto, el código no compila [05:40].
¿Por qué necesito clonar el entity en lugar de asignarlo? Porque Java maneja objetos por referencia. Si no clonas, el
currentValuese actualiza junto con el entity y nunca podrás comparar el estado previo contra el nuevo.
Cómo imprimir oldValue y newValue en consola
Dentro del método @PostPersist y @PostUpdate, imprime ambos valores:
java System.out.println("Post Persist or Update"); System.out.println("oldValue: " + this.currentValue.toString()); System.out.println("newValue: " + entity.toString());
Al probar con Postman cambiando el nombre de la pizza Mother Heart a Holy Mother Heart y alternando la bandera vegan, la consola muestra primero el postLoad, luego la sentencia update de Hibernate, y finalmente el bloque con oldValue y newValue claramente diferenciados [07:50].
Recuerda implementar toString() en PizzaEntity con todos los campos, porque si dejas el toString por defecto solo verás la dirección de memoria del objeto.
Qué pasa al crear un registro nuevo o usar queries nativos
Cuando creas una pizza desde cero, no hay un select previo, así que @PostLoad nunca se ejecuta y currentValue queda en null. Para evitar un NullPointerException debes quitar el .toString() de currentValue o validar que no sea nulo antes de imprimirlo.
En una creación, el oldValue impreso será el último entity que sí pasó por un postLoad en esa instancia del listener, no el de la pizza nueva. Por eso, en el postPersist conviene verificar que el ID de la entidad coincida antes de confirmar que esos cambios pertenecen al mismo registro auditado.
¿Funciona el EntityListener con queries nativos? No. Solo funciona con métodos del ciclo de vida de los Spring Data Repositories como
save. Un@Querynativo de actualización, por ejemplo el de actualizar precio, salta el ciclo de vida y el listener nunca se dispara.
Esta es una limitación importante: si tu aplicación mezcla repositorios estándar con queries nativos, la auditoría quedará incompleta sobre las operaciones nativas. En esos casos tendrías que auditar manualmente desde el servicio o mover esa lógica al ciclo de vida de JPA.
De aquí en adelante, en lugar de imprimir en consola puedes enviar oldValue y newValue a una tabla de auditoría, a un archivo de logs o a un sistema externo de trazabilidad. ¿Cómo estás manejando hoy la auditoría en tus entidades? Cuéntame en los comentarios qué eventos te interesa capturar.