Spring Data JPA Tutorial: Auditing, Part Two

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 previous part of this tutorial described how we can use the auditing infrastructure of Spring Data JPA for finding the answer to the first question.

This blog posts describes how we can find the answer to the second question. We will modify our example application to store the username of the authenticated user who created a new todo entry and updated the information of an existing todo entry.

Let’s start by creating a component that returns the information of the authenticated user.

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 the Information of the Authenticated User

The auditing infrastructure of Spring Data JPA uses the AuditorAware<T> interface when it needs to get the information of the authenticated user. The AuditorAware interface has one type parameter (T) which describes the type of the entity’s field that contains the auditing information.

Because we have to create a class that returns the username of the authenticated user, we have to follow these steps:

  1. Create UsernameAuditorAware class and implement the AuditorAware interface. Because we want to store the username of the authenticated user (String), we must set the value of the type parameter to String.
  2. Implement the getCurrentAuditor() method by following these steps:
    1. Get an Authentication object from the SecurityContext.
    2. Return null if the authentication is not found or the found authentication is not authenticated.
    3. Return the username of the authenticated user.

The source code of the UsernameAuditorAware class looks as follows:

import org.springframework.data.domain.AuditorAware;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;

public class UsernameAuditorAware implements AuditorAware<String> {

    @Override
    public String getCurrentAuditor() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        if (authentication == null || !authentication.isAuthenticated()) {
            return null;
        }

        return ((User) authentication.getPrincipal()).getUsername();
    }
}

Let’s move on and find out how we can configure the application context of our example application.

Configuring the Application Context

We can configure the application context of our application by making the following changes to the configuration class that configures the persistence layer of our application:

  1. Create an auditorProvider() method that returns an AuditorAware<String> object.
  2. Implement the method by creating a new UsernameAuditorAware object.
  3. Annotate the method with the @Bean annotation.
  4. Enable the auditing support of Spring Data JPA by annotating the configuration class with the @EnableJpaAuditing annotation.

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.domain.AuditorAware;
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
	AuditorAware<String> auditorProvider() {
		return new UsernameAuditorAware();
	}
 
    @Bean
    DateTimeProvider dateTimeProvider(DateTimeService dateTimeService) {
        return new AuditingDateTimeProvider(dateTimeService);
    }
}

Because we declared only one AuditorAware bean, the auditing infrastructure finds it automatically and uses it when it has to set the information of the authenticated user to the field(s) of the saved or updated entity object. If we declare multiple AuditorAware beans, we can configure the used bean by setting the value of the auditorAwareRef attribute of the @EnableJpaAuditing annotation.

If you want use XML configuration, you can enable the auditing support by using the auditing element. Take look at the example application of this blog post that has a working XML configuration file.

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):

  1. We need to ensure that the value of the createdByUser field is set when our entity is persisted for the first time.
  2. We need to ensure that the value of the modifiedByUser 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. Add a createdByUser field into the entity class, set its type to String, and follow these steps:
    1. Annotate the field with the @Column annotation. Configure the name of the database column (created_by_user) and ensure that the value of this column cannot be null.
    2. Annotate the field with the @CreatedBy annotation. This identifies the field that contains the information of the user who created the entity.
  2. Add a modifiedByUser field into the entity class, set its type to String, and follow these steps
    1. Annotate the field with the @Column annotation. Configure the name of the database column (modified_by_user) and ensure that the value of this column cannot be null.
    2. Annotate the field with the @LastModifiedBy annotation. This identifies the field that contains the information of the user who made the last changes to the entity.

The relevant part of the Todo class looks as follows:

import org.hibernate.annotations.Type;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
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 = "created_by_user", nullable = false)
    @CreatedBy
    private String createdByUser;

    @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 = "modified_by_user", nullable = false)
    @LastModifiedBy
    private String modifiedByUser;

    @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. 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.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.Column;
import javax.persistence.MappedSuperClass

@EntityListeners(AuditingEntityListener.class)
@MappedSuperClass
public abstract class BaseEntity {
 
	@Column(name = "created_by_user", nullable = false)
	@CreatedBy
	private String createdByUser;
	
	@Column(name = "creation_time", nullable = false)
	@Type(type = "org.jadira.usertype.dateandtime.threeten.PersistentZonedDateTime")
	@CreatedDate
	private ZonedDateTime creationTime; 
 
	@Column(name = "modified_by_user", nullable = false)
	@LastModifiedBy
	private String modifiedByUser;
	
	@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 Infrastructure of Spring Data JPA?

If we want to save the information of the user who created and updated our entity, we don’t have to use Spring Data JPA. We can set the values of these fields (createdByUser and modifiedByUser) by creating callback methods that are attached to the entity lifecycle events.

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

import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.LastModifiedBy;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
 
import javax.persistence.Column;
import javax.persistence.MappedSuperClass
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
 
@MappedSuperClass
public abstract class BaseEntity {
 
	@Column(name = "created_by_user", nullable = false)
	@CreatedBy
	private String createdByUser; 
	
	@Column(name = "modified_by_user", nullable = false)
	@LastModifiedBy
	private String modifiedByUser;

	
    @PrePersist
    public void prePersist() {
		String createdByUser = getUsernameOfAuthenticatedUser();
		this.createdByUser = createdByUser;
		this.modifiedByUser = createdByUser;
    }
     
    @PreUpdate
    public void preUpdate() {
		String modifiedByUser = getUsernameOfAuthenticatedUser();
		this.modifiedByUser = modifiedByUser;
    }
	
	private String getUsernameOfAuthenticatedUser() {
		Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

		if (authentication == null || !authentication.isAuthenticated()) {
			return null;
		}

		return ((User) authentication.getPrincipal()).getUsername();
	}
}

Even though this method is a bit simpler and more straightforward than using the auditing infrastructure of Spring Data JPA, there are two reasons why we should consider using a more complex solution:

First, using callback methods creates a coupling between our base class (or entity classes) and Spring Security, and I want to avoid it.

Second, if we need to set the values of the creation and modification time fields AND we decided to use the auditing infrastructure of Spring Data JPA for that purpose, we should use it for setting the field values of the createdByUser and modifiedByUser fields because it makes no sense to set the audit information by using two different mechanisms.

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

Summary

This blog post has taught us three things:

  • The AuditorAware<T> interface declares the method that provides the information of the authenticated user to the auditing infrastructure of Spring Data JPA.
  • We can identify the audit 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 audit fields by using callback methods that are attached to the entity lifecycle events. The downside of this method is that it creates a coupling between our abstract base class (or entity classes) and Spring Security.

The next part of this tutorial describes how we can add custom methods into a single repository.

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.
26 comments… add one
  • Pradip Aug 5, 2015 @ 15:23

    Hello i am new to spring data jpa, i want make a generic repository using spring data jpa, can you help me

    • Petri Aug 5, 2015 @ 15:35

      If you want to create repository interfaces by using Spring Data JPA, you should read my Spring Data JPA Tutorial (start from the part one). On the other hand, if you want to create a generic Spring repository class that uses JPA, you should read this blog post.

  • Taha Oct 25, 2015 @ 0:39

    Hey its very intersting this blog thank you for this best work :
    i have a question you configure the Application Context with java if i use xml configuration i must to do like this :
    and JpaAuditing bean but what we make inside this bean in xml file??

    • Petri Oct 25, 2015 @ 12:16

      Hi,

      Thank you for your kind words. I really appreciate them!

      i have a question you configure the Application Context with java if i use xml configuration i must to do like this

      The example application of this blog post has a working XML configuration file. Does it answer to your question?

      • Taha Oct 25, 2015 @ 14:05

        yeah that what i want, thank you very much

        • Petri Oct 26, 2015 @ 10:06

          You are welcome.

  • Bananio Feb 2, 2016 @ 17:50

    Hello,

    first of all, great tutorial, it helped me very much!

    one question regarding @preUpdate
    i have an update method in my repository annotated with @query(..) and @modifiying but it does not trigger @preUpdate. (also has @transactional if it matters) any idea why is it not triggered? (method works and updated entry in DB)

    thanks!

  • KL Mar 29, 2016 @ 3:31

    Hi;
    Great tutorial! Thanks.
    One question, with the @EnableJpaAuditing(), shall it include the auditorAwareRef as well?

    Cheers,
    KL

    • Petri Mar 29, 2016 @ 17:51

      You are welcome!

      The answer to your question is yes. I mentioned this on the post as well:

      Because we declared only one AuditorAware bean, the auditing infrastructure finds it automatically and uses it when it has to set the information of the authenticated user to the field(s) of the saved or updated entity object. If we declare multiple AuditorAware beans, we can configure the used bean by setting the value of the auditorAwareRef attribute of the @EnableJpaAuditing annotation.

  • Paul Jul 21, 2016 @ 1:54

    Really liked your tutorial!

    • Petri Jul 21, 2016 @ 12:41

      Thank you for your kind words. I really appreciate them.

  • Sathishkumar Aug 26, 2016 @ 22:24

    Hi,

    Thanks for the tutorial. It is very good and helpful.
    I have implemented this in my application and i face an issue. I jus replicated what you have given in this part two tutorila. While creating a new record am updating the CreateBy, CreateDate, UpdatedBy, UpdateDate through @PrePersist - This works fine and updates the values as required.

    While @PostUpdate, am updating UpdatedBy, UpdateDate only, but it default updates the createBy and CreateDate column as well.

    Please let me know, is there any other setting i need to do here?
    PFB entity which i have modified for auditing.

    @MappedSuperclass
    @EntityListeners(AuditingEntityListener.class)
    public abstract class AuditEntity {

    @Column(name = C_CREATED_BY, nullable = false)
    @CreatedBy
    private String createdBy;

    @Column(name = C_UPDATED_BY, nullable = false)
    @LastModifiedBy
    private String updatedBy;

    @Column(name = C_CREATED_DATE, nullable = false)
    @CreatedDate
    private Date createdDate;

    @Column(name = C_UPDATED_DATE, nullable = false)
    @LastModifiedDate
    private Date updatedDate;

    @PrePersist
    public void prePersist() {
    this.createdBy = "Test";
    this.updatedBy = "Test";
    this.createdDate = new Date();
    this.updatedDate = new Date();
    }

    @PostUpdate
    public void preUpdate() {
    this.updatedBy = "SCOR";
    this.updatedDate = new Date();
    }
    }

    Thanks
    Sathishkumar

    • Petri Sep 5, 2016 @ 21:12

      Hi,

      I have to admit that I don't know what your problem is. That being said, I noticed that your entity uses the Spring Data JPA auditing AND it has the standard JPA callback methods. You shouldn't use both of them in the same class because they can conflict each other. Try to remove the code you don't want to use and see if you can still see the same behavior.

      • Hi Petri, Yes I had the same scenario Sep 23, 2016 @ 22:01

        Hi Petri,
        Yes I had the same scenario wherein I have not used both configurations. I have not used Spring Data JPA annotations. I have used custom annotations with @PrePersist and @PostUpdate. When the insert / update are happening, the @PrePersist and @PostUpdate methods are not getting triggered.

        Sample :
        @PrePersist
        public void prePersist() {
        if (getCreateProgramName() == null) {
        setCreateProgramName("sfbt");
        }
        if (getLastUpdateProgramName() == null) {
        setLastUpdateProgramName("sfbt");
        }
        if (getCreateUserId() == null) {
        setCreateUserId("sftbrf");
        }
        if (getLastUpdateUserId() == null) {
        setLastUpdateUserId("sftbrf");
        }
        setCreateTimestamp(new Date());
        setLastUpdateTimestamp(new Date());
        }

        • Petri Sep 27, 2016 @ 18:09

          Unfortunately it's a bit hard to say what is really going on without debugging the code. Do you have a sample project that reproduces this problem?

  • Tommi Apr 27, 2017 @ 10:51

    How to test this? I did what you have done here and I'm using Spring Security. Now when I made a test for my entity, the @Created annotation works, the date is added to the insert statement but the @CreatedBy annotation doesn't work. In my test, I have a @Before method, where I set new UsernamePasswordAuthenticationToken to securityContextHolder and if I debug, I can see, it is initaliazed.

  • James May 4, 2017 @ 21:07

    Hi Petri,
    Thank you for this article. Let's say I want to maintain a audit / history of entity data changes. So for example, let's say the title field of the Todo entity is changed from "Foo" to "Bar". And then later, the value is changed from "Bar" to "Car". Can audit capabilities of Spring Data JPA persist the history of these changes so that I can query the database and know that the title was changed from "Foo" to "Bar" and then from "Bar" to "Car"? Thanks.

    • Petri May 5, 2017 @ 19:14

      Hi,

      The auditing capabilities of Spring Data JPA don't support your use case. If you are using Hibernate, you should take a look at Hibernate Envers. It provides a "real" auditing / versioning support for your entities.

  • Indika Sep 25, 2017 @ 13:02

    Hi, Many thanks for this wonderful tutorial. Please let me know how to audit modified fields of a given entity. For an example, -> Need to get somthing like this..
    Action Entity User Change
    Modify Employee Smith Changed First Name of employee Id EMP00900 to David
    Modify Employee Alex Changed DOB of employee Id EMP00901 to 12/09/2000
    Remove Employee Alex Removed employee Id EMP00909

  • Nayren Dec 22, 2017 @ 22:11

    Hi Petri, this is a very usefull post... I nedd your help!
    I need to implement an audit system to the entities, with spring, but without adding fields to them, but rather storing this audit information in a log file inside the tomcat folder, for example ... I must store who did the change, the date and to which entity. How can I do this? Do you have any idea?

    • Petri Dec 23, 2017 @ 21:39

      Hi Nayren,

      Do you have to include the changed information to the audit log?

  • Devendra Kumar Dec 17, 2019 @ 18:26

    If I used more than one @CreatedBy annotation in single entity class then it is not working. It is setting only one value rest is ignored.

Leave a Reply