Using audit metadata in Spring Data JPA

Using audit metadata in Spring Data JPA

March 19, 2024
Java, Spring, Spring Data JPA
Java, Spring Boot, Jpa

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.