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:
- Spring Data JPA Tutorial: Introduction provides a quick introduction to Spring Data JPA and gives an overview of the Spring Data repository interfaces.
- Spring Data JPA Tutorial: Getting the Required Dependencies describes how you can get the required dependencies.
- Spring Data JPA Tutorial: Configuration describes how you can configure the persistence layer of a Spring application that uses Spring Data JPA.
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:
- 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.
- 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 DateTimeService interface declares only one method:
- The getCurrentDateAndTime() method returns a ZonedDateTime object.
The source code of the DateTimeService interface looks as follows:
1import java.time.ZonedDateTime;
2
3public interface DateTimeService {
4
5 ZonedDateTime getCurrentDateAndTime();
6}
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:
1import java.time.ZonedDateTime;
2
3public class CurrentTimeDateTimeService implements DateTimeService {
4
5 @Override
6 public ZonedDateTime getCurrentDateAndTime() {
7 return ZonedDateTime.now();
8 }
9}
Additional Reading:
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:
- Create an AuditingDateTimeProvider class and implement the DateTimeProvider interface.
- Add a final DateTimeService field to the created class and inject it by using constructor injection.
- 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:
1import org.springframework.data.auditing.DateTimeProvider;
2
3import java.util.Calendar;
4import java.util.GregorianCalendar;
5
6public class AuditingDateTimeProvider implements DateTimeProvider {
7
8 private final DateTimeService dateTimeService;
9
10 public AuditingDateTimeProvider(DateTimeService dateTimeService) {
11 this.dateTimeService = dateTimeService;
12 }
13
14 @Override
15 public Calendar getNow() {
16 return GregorianCalendar.from(dateTimeService.getCurrentDateAndTime());
17 }
18}
Additional Reading:
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:
- Create the currentTimeDateTimeService() method and implement it by returning a new CurrentTimeDateTimeService object.
- Annotate the method with the @Bean annotation.
- 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:
1import org.springframework.context.annotation.Bean;
2import org.springframework.context.annotation.ComponentScan;
3import org.springframework.context.annotation.Configuration;
4import org.springframework.context.annotation.Import;
5import org.springframework.context.annotation.Profile;
6
7@Configuration
8@ComponentScan("net.petrikainulainen.springdata.jpa")
9@Import({WebMvcContext.class, PersistenceContext.class})
10public class ExampleApplicationContext {
11
12 @Profile(Profiles.APPLICATION)
13 @Bean
14 DateTimeService currentTimeDateTimeService() {
15 return new CurrentTimeDateTimeService();
16 }
17}
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:
- Create a dateTimeProvider() method that returns a DateTimeProvider object and takes a DateTimeService object as a method parameter.
- Implement the method by creating a new AuditingAwareDateTimeProvider object.
- Annotate the created method with the @Bean annotation.
- 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:
1import org.springframework.context.annotation.Bean;
2import org.springframework.context.annotation.Configuration;
3import org.springframework.data.auditing.DateTimeProvider;
4import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
5import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
6import org.springframework.transaction.annotation.EnableTransactionManagement;
7
8@Configuration
9@EnableJpaAuditing(dateTimeProviderRef = "dateTimeProvider")
10@EnableJpaRepositories(basePackages = {
11 "net.petrikainulainen.springdata.jpa.todo"
12})
13@EnableTransactionManagement
14class PersistenceContext {
15
16 @Bean
17 DateTimeProvider dateTimeProvider(DateTimeService dateTimeService) {
18 return new AuditingDateTimeProvider(dateTimeService);
19 }
20}
Additional Reading:
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):
- We need to ensure that the value of the creationTime field is set when our entity is persisted for the first time.
- 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:
- 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.
- 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.
- 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:
1import org.hibernate.annotations.Type;
2import org.springframework.data.annotation.CreatedDate;
3import org.springframework.data.annotation.LastModifiedDate;
4import org.springframework.data.jpa.domain.support.AuditingEntityListener;
5
6import javax.persistence.Column;
7import javax.persistence.Entity;
8import javax.persistence.EntityListeners;
9import javax.persistence.GeneratedValue;
10import javax.persistence.GenerationType;
11import javax.persistence.Id;
12import javax.persistence.Table;
13import javax.persistence.Version;
14import java.time.ZonedDateTime;
15
16@Entity
17@EntityListeners(AuditingEntityListener.class)
18@Table(name = "todos")
19final class Todo {
20
21 @Id
22 @GeneratedValue(strategy = GenerationType.AUTO)
23 private Long id;
24
25 @Column(name = "creation_time", nullable = false)
26 @Type(type = "org.jadira.usertype.dateandtime.threeten.PersistentZonedDateTime")
27 @CreatedDate
28 private ZonedDateTime creationTime;
29
30 @Column(name = "description", length = 500)
31 private String description;
32
33 @Column(name = "modification_time")
34 @Type(type = "org.jadira.usertype.dateandtime.threeten.PersistentZonedDateTime")
35 @LastModifiedDate
36 private ZonedDateTime modificationTime;
37
38 @Column(name = "title", nullable = false, length = 100)
39 private String title;
40
41 @Version
42 private long version;
43}
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:
1import org.hibernate.annotations.Type;
2import org.springframework.data.annotation.CreatedDate;
3import org.springframework.data.annotation.LastModifiedDate;
4import org.springframework.data.jpa.domain.support.AuditingEntityListener;
5
6import javax.persistence.Column;
7import javax.persistence.Entity;
8import javax.persistence.EntityListeners;
9import javax.persistence.MappedSuperClass
10import java.time.ZonedDateTime;
11
12@EntityListeners(AuditingEntityListener.class)
13@MappedSuperClass
14public abstract class BaseEntity {
15
16 @Column(name = "creation_time", nullable = false)
17 @Type(type = "org.jadira.usertype.dateandtime.threeten.PersistentZonedDateTime")
18 @CreatedDate
19 private ZonedDateTime creationTime;
20
21 @Column(name = "modification_time")
22 @Type(type = "org.jadira.usertype.dateandtime.threeten.PersistentZonedDateTime")
23 @LastModifiedDate
24 private ZonedDateTime modificationTime;
25}
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:
- The Javadoc of the Auditable interface
- The Javadoc of the AbstractAuditable class
- The Javadoc of the AuditingEntityListener class
- The Javadoc of the @CreatedDate annotation
- The Javadoc of the @EntityListeners annotation
- The Javadoc of the @LastModifiedDate annotation
- The Javadoc of the @MapperSuperClass annotation
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:
1import org.hibernate.annotations.Type;
2
3import javax.persistence.Column;
4import javax.persistence.Entity;
5import javax.persistence.MappedSuperClass;
6import javax.persistence.PrePersist;
7import javax.persistence.PreUpdate;
8import java.time.ZonedDateTime;
9
10@MappedSuperClass
11public abstract class BaseEntity {
12
13 @Column(name = "creation_time", nullable = false)
14 @Type(type = "org.jadira.usertype.dateandtime.threeten.PersistentZonedDateTime")
15 private ZonedDateTime creationTime;
16
17 @Column(name = "modification_time")
18 @Type(type = "org.jadira.usertype.dateandtime.threeten.PersistentZonedDateTime")
19 private ZonedDateTime modificationTime;
20
21 @PrePersist
22 public void prePersist() {
23 ZonedDateTime now = ZonedDateTime.now();
24 this.creationTime = now;
25 this.modificationTime = now;
26 }
27
28 @PreUpdate
29 public void preUpdate() {
30 this.modificationTime = ZonedDateTime.now();
31 }
32}
Additional Reading:
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.