Spring Data JPA Tutorial: Auditing, Part One

When we hear the word auditing, the first thing that comes to mind is an audit log that contains each version of the audited entity. Implementing an audit log is a complex task that takes a lot of time. Luckily, most of the time we don’t need to do it.

However, it is quite common that we must be able to answer to the following questions:

  • When the entity X was created and/or modified?
  • Who created and/or modified the entity X?

The auditing infrastructure of Spring Data JPA helps us to answer to these questions. This blog post describes how we can add the creation and modification time fields into our entities and update them by using the auditing infrastructure of Spring Data JPA.

Let’s start by creating a service that returns the current date and time.

Additional Reading:

If you are not familiar with Spring Data JPA, you should read the following blog posts before you continue reading this blog post:

Getting Current Date and Time

There are two reasons why we should create an interface that can be used for obtaining the current date and time. These reasons are:

  1. We want to create two different implementations for this interface:
    • The first implementation is used by our application and it returns the current date and time.
    • The second implementation is used by our integration tests and it returns always the same date and time.
  2. If we are implementing a real-life application, the odds are that our other components need to get the current date and time as well.
The blog post titled: Spring Data JPA Tutorial: Integration Testing describes how you can write integration tests for your Spring Data JPA repositories.

The DateTimeService interface declares only one method:

  • The getCurrentDateAndTime() method returns a ZonedDateTime object.

The source code of the DateTimeService interface looks as follows:

import java.time.ZonedDateTime;

public interface DateTimeService {

    ZonedDateTime getCurrentDateAndTime();
}

The CurrentTimeDateTimeService class implements the DateTimeService interface. Its getCurrentDateAndTime() method simply returns the current date and time.

The source code of the CurrentTimeDateTimeService looks as follows:

import java.time.ZonedDateTime;

public class CurrentTimeDateTimeService implements DateTimeService {

    @Override
    public ZonedDateTime getCurrentDateAndTime() {
        return ZonedDateTime.now();
    }
}

Let’s move on and find out how we can integrate our service with the auditing infrastructure of Spring Data JPA.

Integrating Our Service With the Auditing Infrastructure of Spring Data JPA

The auditing infrastructure of Spring Data JPA uses the DateTimeProvider interface when it needs to get the current date and time. This means that if we want to integrate our DateTimeService with Spring Data JPA, we need to implement that interface. We can do this by following these steps:

  1. Create an AuditingDateTimeProvider class and implement the DateTimeProvider interface.
  2. Add a final DateTimeService field to the created class and inject it by using constructor injection.
  3. Implement the getNow() method. We need to fetch the current date and time by using the DateTimeService object and return a new GregorianCalendar object.

The source code of the AuditingDateTimeProvider class looks as follows:

import org.springframework.data.auditing.DateTimeProvider;

import java.util.Calendar;
import java.util.GregorianCalendar;

public class AuditingDateTimeProvider implements DateTimeProvider {

    private final DateTimeService dateTimeService;

    public AuditingDateTimeProvider(DateTimeService dateTimeService) {
        this.dateTimeService = dateTimeService;
    }

    @Override
    public Calendar getNow() {
        return GregorianCalendar.from(dateTimeService.getCurrentDateAndTime());
    }
}

Our next step is to configure the application context of our application. Let’s find out how we can do it.

Configuring the Application Context

First, we need to create a DateTimeService bean that is used when we run our application. We should declare this bean in the root application context configuration class (or XML configuration file) because it is probably used by more than one component, and I think that the root application context configuration class (or XML configuration file) is a natural place for this kind of beans.

We can create this bean by following these steps:

  1. Create the currentTimeDateTimeService() method and implement it by returning a new CurrentTimeDateTimeService object.
  2. Annotate the method with the @Bean annotation.
  3. Annotate the method with the @Profile annotation and set its value to Profiles.APPLICATION. This ensures that this bean is created only when our application is started.

The relevant part of the ExampleApplicationContext class looks as follows:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Profile;

@Configuration
@ComponentScan("net.petrikainulainen.springdata.jpa")
@Import({WebMvcContext.class, PersistenceContext.class})
public class ExampleApplicationContext {

    @Profile(Profiles.APPLICATION)
    @Bean
    DateTimeService currentTimeDateTimeService() {
        return new CurrentTimeDateTimeService();
    }
}
The Profiles is a simple class that defines the "legal" Spring profiles of our example application.

Additional Reading:

Second, we need to create the DateTimeProvider bean and enable the auditing support of Spring Data. We can do this by making the following changes to the configuration class that configures the persistence layer of our example application:

  1. Create a dateTimeProvider() method that returns a DateTimeProvider object and takes a DateTimeService object as a method parameter.
  2. Implement the method by creating a new AuditingAwareDateTimeProvider object.
  3. Annotate the created method with the @Bean annotation.
  4. Annotate the configuration class with the @EnableJpaAuditing annotation and set the name of the DateTimeProvider bean (dateTimeProvider) as the value of of its dataTimeProviderRef attribute.

The relevant part of the PersistenceContext class looks as follows:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.auditing.DateTimeProvider;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;


@Configuration
@EnableJpaAuditing(dateTimeProviderRef = "dateTimeProvider")
@EnableJpaRepositories(basePackages = {
        "net.petrikainulainen.springdata.jpa.todo"
})
@EnableTransactionManagement
class PersistenceContext {

    @Bean
    DateTimeProvider dateTimeProvider(DateTimeService dateTimeService) {
        return new AuditingDateTimeProvider(dateTimeService);
    }
}

Let's move on and make the required changes to our entity class.

Modifying Our Entity Class

We need to make the following changes to our entity class (Todo):

  1. We need to ensure that the value of the creationTime field is set when our entity is persisted for the first time.
  2. We need to ensure that the value of the modificationTime field is set when our entity is persisted for the first time and updated when the information of our entity is updated.

We can make these changes by following these steps:

  1. Annotate the creationTime field with the @CreatedDate annotation. This identifies the field whose value is set when the entity is persisted to the database for the first time.
  2. Annotate the modificationTime field with the @LastModifiedDate annotation. This identifies the field whose value is set when the entity is persisted for the first time and updated when the information of the entity is updated.
  3. Annotate the entity class with the @EntityListeners annotation and set its value to AuditingEntityListener.class. The AuditingEntityListener class is a JPA entity listener that updates the audit information of an entity when it is persisted and updated.

The relevant part of the Todo class looks as follows:

import org.hibernate.annotations.Type;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Version;
import java.time.ZonedDateTime;

@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "todos")
final class Todo {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "creation_time", nullable = false)
    @Type(type = "org.jadira.usertype.dateandtime.threeten.PersistentZonedDateTime")
    @CreatedDate
    private ZonedDateTime creationTime;

    @Column(name = "description", length = 500)
    private String description;

    @Column(name = "modification_time")
    @Type(type = "org.jadira.usertype.dateandtime.threeten.PersistentZonedDateTime")
    @LastModifiedDate
    private ZonedDateTime modificationTime;

    @Column(name = "title", nullable = false, length = 100)
    private String title;

    @Version
    private long version;
}

Typically it is a good idea to add the audit fields into an abstract base class and annotate it with the @EntityListener annotation. The reason why I didn’t do it here is that our example application has only one entity, and I wanted to keep things as simple as possible.

If we would move this information to the abstract base class, its source code would look as follows:

import org.hibernate.annotations.Type;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperClass
import java.time.ZonedDateTime;

@EntityListeners(AuditingEntityListener.class)
@MappedSuperClass
public abstract class BaseEntity {

    @Column(name = "creation_time", nullable = false)
    @Type(type = "org.jadira.usertype.dateandtime.threeten.PersistentZonedDateTime")
    @CreatedDate
    private ZonedDateTime creationTime;

    @Column(name = "modification_time")
    @Type(type = "org.jadira.usertype.dateandtime.threeten.PersistentZonedDateTime")
    @LastModifiedDate
    private ZonedDateTime modificationTime;
}
If you don’t want to use annotations, your entities must either implement the Auditable interface or extend the AbstractAuditable class. The Auditable interface declares getter and setter methods for all audit fields. The AbstractAuditable class provides implementations for these methods, but its drawback is that it creates a coupling between your entities and Spring Data.

Additional Reading:

Let’s find out why we should use the auditing support of Spring Data JPA instead of the callback methods specified in the Java Persistence API.

Why Should We Use the Auditing Support of Spring Data JPA?

If we need to add the creation and modification time fields into our entities, we don’t have to use Spring Data JPA. We can set the field values of these fields by creating callback methods that are attached to the entity lifecycle events.

The source of an abstract base class, which uses this method, looks as follows:

import org.hibernate.annotations.Type;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.MappedSuperClass;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import java.time.ZonedDateTime;

@MappedSuperClass
public abstract class BaseEntity {

    @Column(name = "creation_time", nullable = false)
    @Type(type = "org.jadira.usertype.dateandtime.threeten.PersistentZonedDateTime")
    private ZonedDateTime creationTime;

    @Column(name = "modification_time")
    @Type(type = "org.jadira.usertype.dateandtime.threeten.PersistentZonedDateTime")
    private ZonedDateTime modificationTime;
	
	@PrePersist
	public void prePersist() {
		ZonedDateTime now = ZonedDateTime.now();
		this.creationTime = now;
		this.modificationTime = now;
	}
	
	@PreUpdate
	public void preUpdate() {
		this.modificationTime = ZonedDateTime.now();
	}
}

This method is a lot simpler than the solution that uses the auditing infrastructure of Spring Data JPA. The obvious question is:

Does it make any sense to use a more complex solution?

It depends. There are two reasons why it makes sense:

  • If we have to write tests which ensure that the creation and modification times are correct, we have to use the auditing infrastructure of Spring Data JPA because it gives us the possibility to use a DateTimeProvider that always returns the same date and time.
  • If we need to save the information of the user who created and/or modified an entity, we should use Spring Data for setting the creation and modification times as well. It just doesn’t make any sense to set the audit information of an entity by using two different mechanisms.

Let’s move on and summarize what we learned from this blog post.

Summary

This blog post has taught us three things:

  • We can create our own date and time provider by implementing the DateTimeProvider interface. This is useful because it gives us the possibility to use a different provider for testing purposes.
  • We can identify the creation and modification time fields by using annotations, set the values by implementing the Auditable interface, or extend the AbstractAuditable class.
  • It is simpler to set the values of the creation and modification time fields by using entity lifecycle events and callback methods, but there are situations where we should use the auditing infrastructure of Spring Data JPA (even though it is a more complex solution).

The next part of this tutorial describes how we can get the answer to the question: who created and/or modified the entity X.

P.S. You can get the example application of this blog post from Github.

If you want to learn how to use Spring Data JPA, you should read my Spring Data JPA tutorial.
22 comments… add one
  • SKS Feb 29, 2016 @ 10:41

    Nice, thanks.

    • Petri Feb 29, 2016 @ 12:43

      You are welcome.

  • Ronnie Wang Feb 29, 2016 @ 10:44

    Hi Petri
    I use JDK7. Don't have the ZoneDateTime interface.Is there any other method to use Spring Data Jpa auditing?

    thanks

    • Petri Feb 29, 2016 @ 12:41

      Hi Ronnie,

      You can use the "old" Date objects as well. Just replace ZonedDateTime with Date and you are good to go.

      • Ronnie Wang Mar 1, 2016 @ 4:37

        Hi Petri:

        It's work, thanks again!

        • Petri Mar 1, 2016 @ 20:10

          Hi Ronnie,

          You are welcome!

  • Hannes Smit Apr 9, 2016 @ 15:39

    Hi Petri,
    Thanks for you tutorial. Why did you name the first bean after the implementation and the second after the interface?

    • Petri Apr 9, 2016 @ 16:06

      Hi Hannes,

      I assume that the first bean means the CurrentTimeDateTimeService class and the second bean means the AuditingDataTimeProvider class.

      The actual application has two beans that implement the DateTimeService interface:

      • The CurrentTimeDateTimeService class is used when the application context is loaded in an application mode (the Spring profile application is active).
      • The ConstantTimeDateTimeService class is used when the application context is loaded in an integration test mode (the Spring profile integrationtest is active).

      Because I wanted to separate these two classes, I gave them names that describe the implementation.

      The name of the AuditingDataTimeProvider is not optimal (IMO). I gave it this name because I wanted to describe that it is used by the auditing infrastructure of Spring Data JPA (and I couldn't figure out a better name for it).

  • Connor Aug 1, 2016 @ 2:53

    Petri,

    Per usual, great article!

    I did notice however that when I attempt to implement your solution, I run into a bit of trouble with the AuditingDateTimeProvider.class.

    The method is missing a return type but it's implicitly 'void' so no biggie there. But then it doesn't allow me to set the field as final. Attempting to auto wire the service also gives me some runtime issues.

    Any thoughts?

    • Petri Aug 2, 2016 @ 13:51

      Hi Connor,

      Which method are you talking about? The reason why I ask that the only "method" of the AuditingDataTimeProvider class that doesn't have a return type is the constructor. This also explains why cannot mark the field as final. Do you have a typo in the class name or in the constructor name?

  • Cristian Chies Apr 5, 2017 @ 16:43

    Just saved my day!! thanks a ton!!!
    Best regards from Brazil!!!

  • Anonymous Jan 18, 2018 @ 14:12

    Hi, i feel the auditing provided by Hibernate envers is more exhaustive where one can track the revisions done to data, The example explains a basic audit of created by created date, modified by and modified date, does the spring jpa data auditing ensure revisions are maintained in the audit table

    • Petri Jan 18, 2018 @ 17:45

      Hi,

      You are right. If you need "real" auditing that tracks revisions, you should use Hibernate Envers (or some other similar tool).

  • asdc Sep 13, 2018 @ 20:40

    poor maming convention and confusing

    • Petri Sep 14, 2018 @ 8:14

      Hi,

      Could you clarify your feedback? At the moment it's not very useful because I have no idea what is the target of your criticism (Spring Data JPA or my example code). In any case, I hope to hear from you because it's always interesting to learn from my readers.

  • Billy Dec 6, 2018 @ 20:50

    Can i ask how his approach could be adapted if we want to save the audit entries to a separate audit table that captures the id of the entry, along with dates and old/new values of modified object?

    • Billy Dec 6, 2018 @ 22:17

      Seems that Hibernate Envers is a more suitable choice for this

      • Petri Dec 7, 2018 @ 10:04

        Hi,

        You are correct. If you need to create a "real" audit log, Hibernate Envers is a good choice.

Leave a Reply