Tercera parte del artículo dedicado a la persistencia en BD con Spring Data JPA:
- Primeros pasos
- Repositorios personalizados
- Auditoría
Spring Data JPA proporciona un mecanismo simple y elegante para dotar de un sistema automático de auditoría a las entidades. Los campos de auditoría contemplados son la fecha de creación, fecha de última modificación, el usuario creador y el último que modificó la entidad. Vamos a implementar este mecanismo en nuestro ejemplo paso a paso.
- Comprobar que tenemos la dependencia spring-aspects. En función de los módulos de Spring que estemos usando es muy posible que ya la tengamos, si no la añadimos:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>{spring.version}</version> <dependency>
En cualquier caso, si no tenemos esta librería en el classpath la excepción que se produce es muy descriptiva:
Configuration problem: Could not configure Spring Data JPA auditing-feature because spring-aspects.jar is not on the classpath!
- Añadir los campos de auditoría a las entidades. Generalmente crearemos una superclase con estos campos que será especializada por todas las entidades que requieran auditoría. Estos atributos se anotarán con @CreatedBy, @LastModifiedBy, @CreatedDate y @LastModifiedDate. Para las fechas se puede utilizar Date, Calendar, los DateTime de Joda-Time o la nueva API de fechas de Java 8, y para el usuario cualquier clase. En el ejemplo se utilizará un String por simplicidad pero en una aplicación real lo habitual será utilizar una clase User o similar del modelo de datos.
package com.danielme.demo.springdatajpa.model; import java.util.Calendar; import javax.persistence.Column; import javax.persistence.EntityListeners; import javax.persistence.MappedSuperclass; import javax.persistence.Temporal; import javax.persistence.TemporalType; import org.springframework.data.annotation.CreatedBy; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedBy; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; @EntityListeners({ AuditingEntityListener.class }) @MappedSuperclass public class AuditableEntity { @Column(nullable = false, updatable = false) @CreatedDate @Temporal(TemporalType.TIMESTAMP) private Calendar createdDate; @LastModifiedDate @Temporal(TemporalType.TIMESTAMP) private Calendar lastModifiedDate; @Column(nullable = false, updatable = false) @CreatedBy private String createBy; @LastModifiedBy private String lastModifiedBy; ...getters y setters
Esta clase además está anotada con @MappedSuperclass para que las entidades hijas incluyan sus atributos en la tabla correspondiente y con @EntityListeners para que se ejecute el listener de auditoría de Spring Data. Si hacemos que Country herede de AuditableEntity el modelo de datos quedará tal que así:
Una alternativa consiste en hacer que la entidades auditables hereden de la clase AbstractAuditable de Spring Data JPA, equivalente a la clase AuditableEntity que hemos creado, o incluso hacer que implementen directamente la interfaz Auditable. En ambos casos perdemos flexibilidad y no obtenemos ninguna ventaja aparente salvo que puntualmente haya que utilizar la interfaz al no disponer Java de herencia múltiple.
- Implementar la interfaz AuditorAware. Esta implementación será un bean de Spring por lo que se anotará con @Component y tendrá un método que devolverá un objeto correspondiente al usuario que se utilizará en los campos de auditoría (en una misma transacción este método sólo se ejecuta la primera vez que sea necesario obtener el usuario). En el ejemplo el nombre de usuario se tomará de un atributo estático, en una aplicación real se obtendrá del sistema de gestión de usuarios que tengamos implementado, por ejemplo Spring Security.
package com.danielme.demo.springdatajpa.listeners; import java.util.Optional; import org.springframework.data.domain.AuditorAware; import org.springframework.stereotype.Component; import com.danielme.demo.springdatajpa.AuthenticationMockup; @Component public class CustomAuditorAware implements AuditorAware<String> { @Override public Optional<String> getCurrentAuditor() { return Optional.of(AuthenticationMockup.UserName); } }
- Definir nuestro AuditorAware en al applicationContext.xml de la siguiente forma:
<jpa:auditing auditor-aware-ref="customAuditorAware" />
De forma programática:
@Configuration @PropertySource("classpath:db.properties") @ComponentScan("com.danielme") @EnableTransactionManagement @EnableJpaRepositories(basePackages="com.danielme.demo.springdatajpa.repository", repositoryFactoryBeanClass = CustomBaseRepositoryFactoryBean.class) @EnableJpaAuditing(auditorAwareRef="customAuditorAware") public class ApplicationContext {
Siguiendo estos pasos ya tenemos la auditoría configurada. Se puede probar con el siguiente test:
@Test public void testAudit() { AuthenticationMockup.UserName = "dani"; Country country = countryRepository.save(new Country("Bolivia", 10556105)); assertTrue(country.getCreateBy().equals(country.getLastModifiedBy())); assertTrue(country.getCreatedDate().equals(country.getLastModifiedDate())); AuthenticationMockup.UserName = "update"; country.setName("Estado Plurinacional de Bolivia"); country = countryRepository.save(country); assertTrue(country.getLastModifiedBy().equals(AuthenticationMockup.UserName)); assertTrue(country.getCreateBy().equals("dani")); assertFalse(country.getCreatedDate().equals(country.getLastModifiedDate())); }
Código de ejemplo
El proyecto completo se encuentra disponible en GitHub. Para más información sobre cómo utilizar GitHub, consultar este artículo.
Muy buen articulo.
Si se usa la implementacion de hibernate y se quiere una auditoria de los datos cambiados de una BBDD, se puede usar Hibernate Envers.
Os dejo un buen enlace que encontré hace tiempo
https://jrodriguezweb.wordpress.com/2013/10/29/hibernate-envers-ejemplo/
Buenas, no tengo applicationcontext, y estoy usando spring boot