Java

Auditing with Spring

When accessing databases, it’s occasionally important to determine who made a change to an entity and when. The recording of this metadata is called auditing.

 

Jakarta Persistence knows @EntityListeners, for example. This can be used to add information before writing an entity, for example, for the last access. Of course, we could implement auditing this way manually, but there are better approaches.

 

Auditing with Spring Data

Spring Data supports simple auditing out of the box. Moreover, this isn’t specific to Spring Data JPA but is also supported in other Spring Data family members, including MongoDB.

 

For auditing, metadata must be written to columns and fields. There are two ways to mark these places:

  • Declaratively via annotations: There are @CreatedBy (“Who created the data?”), @LastModifiedBy (“Who modified the data last?”), @CreatedDate (“When was the data created?”), and @LastModifiedDate (“When was the data last modified?”) annotations.
  • Code implementation of interface Auditable or by extending AbstractAuditable: Interface Auditable declares various setters/getters, and AbstractAuditable offers the persistent attributes directly. 

Auditing with Spring Data JPA

Let’s look at how auditing can be used in the case of Spring Data JPA.

@EnableJpaAuditing

First, you have to set @EnableJpaAuditing to a configuration, such as @SpringBootApplication:

 

@SpringBootApplication

@EnableJpaAuditing

public class Data4uApplication { … }7

AuditingEntityListener

If Jakarta Persistence is used, the implementation works through a predefined entity listener AuditingEntityListener. The task of the AuditingEntityListener is to look at the persistent attributes, find out if they are annotated, and, if so, update or set them accordingly.

 

An entity listener is set via @EntityListeners:

 

@Entity

@EntityListeners( AuditingEntityListener.class )

class Photo …

 

The entity, when using annotations, gets the persistent attributes assigned with @CreatedBy, @LastModifiedBy, @CreatedDate, or @LastModifiedDate. Here’s an example:

 

@Column( nullable = false, updatable = false )

@CreatedDate

private LocalDateTime created;

 

@Column( nullable = false )

@LastModifiedDate

private LocalDateTime updatedAt;

 

Spring Data auditing allows recording the creation and modification time of entities in persistent attributes. The @CreatedDate column is always occupied and hence not nullable. During an update, the @CreatedDate column need not be updated because it’s only written once during insertion. On the other hand, the @LastModifiedDate column will be written several times during the lifecycle and hence can’t be null. Therefore, setting updatable = false for the @LastModifiedDate column would be false.

 

AuditorAware for User Information

Of the four annotation types—@CreatedBy, @LastModifiedBy, @CreatedDate, @LastModifiedDate—two have the component “Date”, which says that a timestamp is written. The other two annotation types contain “By”, which is where a user identifier is written. However, the question is, how does the framework get the current user identifier?

 

By default, Spring can’t automatically determine a user identifier—we have to help. To use @CreatedBy and @LastModifiedBy, a component of type AuditorAware<String> must supply the name. The declaration of the interface looks like this:

 

public interface AuditorAware<T> {

   Optional<T> getCurrentAuditor();

}

 

Now let’s look at two examples of how a Spring-managed bean of type AuditorAware can be created:

Example 1

The username is taken from the system property:

 

@Bean

AuditorAware<String> systemUserNameAuditorAware( Environment env ) {

   return () -> Optional.ofNullable( env.getProperty( "user.name" ) );

}

 

The first implementation gets the username from the system properties. On a server, this makes little sense because a username would not depend on the logged-in user.

Example 2

The username is taken from SecurityContextHolder:

 

@Bean

AuditorAware<String> securityContextHolderAuditorAware() {

   return () -> Optional.ofNullable(

       SecurityContextHolder.getContext().getAuthentication().getName()

   );

}

 

The second variant uses SecurityContext, from which you can get an Authentication object that returns a name via the getName() method. However, it’s a prerequisite that an application uses Spring Security; otherwise, the data types aren’t available.

 

Outlook: Spring Data Envers

Spring Data auditing has two significant limitations that may not be suitable for real auditing purposes. First, it doesn’t provide any information about who made specific modifications, what exactly was changed, and at which time. Although metadata about the logged-in user and the timestamp is available, it doesn’t offer sufficient details about the changes made. Secondly, the metadata about users and times is stored alongside the entity. Therefore, if data is deleted, all information is lost.

 

Although the Spring Data auditing approach is sufficient for displaying information such as when a page was created or last modified, it may not be suitable for real auditing requirements. Fortunately, there are alternative approaches such as the Hibernate Envers project (https://hibernate.org/orm/envers/) project, which addresses these limitations by using extra tables to store the changed data.

7

The project Spring Data Envers integrates Hibernate Envers into the Spring Framework. The process is straightforward, where you need to set the @Audited annotation to the entity bean, and the rest is done automatically. Hibernate Envers will create new tables and record who changed which row and when, which includes the entire history of all changes. If any persistent attributes should not be recorded, they can be annotated with @NotAudited.

 

However, the fact that Hibernate Envers writes all changes to tables isn’t the only benefit. There is also a revision repository that can be used to query and track the changes made. This is an essential feature for auditing purposes, as it provides a complete history of all changes made to the entities. For detailed information, it’s recommended to refer to the reference documentation of the Hibernate site.

 

Note: When all changes to rows are managed on the software side, the costs can increase significantly because every modification automatically triggers a backup of the data in the revision tables in the background. Professional database management systems typically provide native auditing capabilities, which can make the auditing process more efficient because the code and tables are closely integrated with the database management system. However, Hibernate Envers has a notable advantage in terms of portability because it can work with any RDBMS. This means that it’s not tied to a specific database management system and can be used across multiple platforms, making it a flexible and adaptable auditing solution.

 

Editor’s note: This post has been adapted from a section of the book Spring Boot 3 and Spring Framework 6 by Christian Ullenboom.

Recommendation

Spring Boot 3 and Spring Framework 6
Spring Boot 3 and Spring Framework 6

Say goodbye to dependencies, bogged-down code, and inflexibility! With the Spring framework and Spring Boot, you’ll painlessly create Java applications that are production ready. Start with the basics: containers for Spring-managed beans, Spring framework modules, and proxies. Then learn to connect to relational databases, implement Jakarta Persistence, use Spring Data JPA, and work with NoSQL databases. Get the right know-how for modern software development with Spring and Java!

Learn More
Rheinwerk Computing
by Rheinwerk Computing

Rheinwerk Computing is an imprint of Rheinwerk Publishing and publishes books by leading experts in the fields of programming, administration, security, analytics, and more.

Comments