Using audit metadata in Spring Data JPA
March 19, 2024
Introduction #
While persisting objects there may be the need to save some extra generated information that the user does not need to care about but which is nevertheless relevant to some backend process or business need. In this article we explain how one can enable such information so that it is persisted along with the object state.
Activation #
In order to activate metadata recording in Spring Data JPA some configuration changes should be made:
-
Activate the corresponding listener in orm.xml, to configure the underlying JPA (Java Persistence API) implementation (ex. Hibernate or EclipseLink).
-
Make sure Spring Aspects dependency is in the classpath.
-
Activate auditing in your application.
Activating the entity listener #
Spring Data JPA uses an entity listener that is responsible for adding/updating audit metadata. The underlying JPA implementation needs to be aware of this listener.
That is done by using src/main/resources/META-INF/orm.xml:
<entity-mappings version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm
http://xmlns.jcp.org/xml/ns/persistence/orm_2_1.xsd">
<persistence-unit-metadata>
<persistence-unit-defaults>
<entity-listeners>
<entity-listener
class="org.springframework.data.jpa.domain.support.AuditingEntityListener"/>
</entity-listeners>
</persistence-unit-defaults>
</persistence-unit-metadata>
</entity-mappings>
It is worth noting that this orm.xml change activates auditing for all entities.
If there is a need to enable it only for a subset of entities one can use the following annotation instead:
@Entity
@EntityListeners(AuditingEntityListener.class)
public class SomeEntity {
...
}
Adding Spring Aspects #
As the listener relies on AOP mechanism to enrich entities with metadata it needs Spring Aspects jar in the classpath.
The easiest way is to have the dependency managed through a Spring Boot Starter.
Just make sure you are using spring-boot-starter-data-jpa Maven dependency:
<dependency>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<groupId>org.springframework.boot</groupId>
</dependency>
If you still want/need to add the Maven dependency manually you can check Spring Aspects on Maven Repository
Activating JPA Auditing in your application #
Once entity listener is added it is enough to use the annotation org.springframework.data.jpa.repository.config.EnableJpaAuditing on your main application entry point:
@SpringBootApplication
@EnableJpaAuditing
public class SomeApplication {
public static void main(String[] args) {
SpringApplication.run(
SomeApplication.class,
args);
}
}
As with many other Spring Boot enabling annotations it is possible to use any other configuration class for that purpose.
@Configuration
@EnableJpaAuditing
class SomeConfig {
...
}
Using the Auditing mechanism #
The last step is to decide which audit information should be saved on your entities.
Spring Data JPA provides the following annotations to capture some basic information:
- org.springframework.data.annotation.CreatedBy
The user who created the entity
- org.springframework.data.annotation.LastModifiedBy
The user who last changed the entity
- org.springframework.data.annotation.CreatedDate
The date when the entity was created
- org.springframework.data.annotation.LastModifiedDate
The date when the entity was last modified
User dependent annotations need context!
@LastModifiedBy and @CreatedBy depend on the session user being available. An extra configuration will be needed for that. See Spring Data JPA Reference Documentation for more details.
The auditing info can then be used directly at the entity level:
@Entity
public class SomeEntity{
@CreatedDate
private Instant createdDate;
@LastModifiedDate
private Instant modifiedDate;
}
For a cleaner separation one can also group all metadata information in an embeddable class:
@Entity
public class SomeEntity{
private AuditMetadata auditInfo;
@Embeddable
static class AuditMetadata {
@CreatedDate
private Instant createdDate;
@LastModifiedDate
private Instant modifiedDate;
}
}
The change in both cases will translate into a relational table for SomeEntity with two extra columns for createdDate and modifiedDate fields, that are automatically populated by Spring Data JPA.