Spring Data JPA Auditing: Saving CreatedBy, CreatedDate, LastModifiedBy, LastModifiedDate automatically

In any business application auditing simply means tracking and logging every change we do in the persisted records which simply means tracking every insert, update and delete operation and storing it.

Auditing helps us in maintaining history records which can later help us in tracking user activities. If implemented properly auditing can also provide us similar functionality like version control systems.

I have seen projects storing these things manually and doing so become very complex because you will need to write it completely by your own which will definitely require lots of code and lots of code means less maintainability and less focus on writing business logic.

But why should someone need to go to this path when both JPA and Hibernate provides Automatic Auditing which we can be easily configured in your project.

And here in this article, I will discuss how we can configure JPA to persist CreatedBy, CreatedDate, LastModifiedBy, LastModifiedDate columns automatically for any entity.

Spring-Data-JPA-Automatic-Auditing-Saving-CreatedBy-CreatedDate-LastModifiedBy-LastModifiedDate-Automatically


I will walk you through to the necessary steps and code portions you will need to include in your project to automatically update these properties. We will use Spring Boot, Spring Data JPA, MySql to demonstrate this. We will need to add below parent and dependencies to our pom file

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.1.RELEASE</version>
    <relativePath/>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

Spring Data Annotations @CreatedBy, @CreatedDate, @LastModifiedBy and @LastModifiedDate


Let’s suppose we have a File entity and a single record in file table stores name and content of the file and we also want to store who created and modified any file at what time. So we can keep track like when the file was created by whom and when it was last modified by whom.

So we will need to add name, content, createdBy, createdDate, lastModifiedBy, lastModifiedDate properties to our File entity and to make it more appropriate we can move createdBy, createdDate, lastModifiedBy, lastModifiedDate properties to a base class Auditable and annotate this base class by @MappedSuperClass and later we can use the Auditable class in other audited entities.

You will also need to write getters, setters, constructors, toString, equals along with these fields. However, you should take a look at Project Lombok: The Boilerplate Code Extractor, if you want to auto-generate these things.

Both classes will look like

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class Auditable<U> {

    @CreatedBy
    protected U createdBy;

    @CreatedDate
    @Temporal(TIMESTAMP)
    protected Date creationDate;

    @LastModifiedBy
    protected U lastModifiedBy;

    @LastModifiedDate
    @Temporal(TIMESTAMP)
    protected Date lastModifiedDate;

    // Getters and Setters

}

@Entity
public class File extends Auditable<String> {
    @Id
    @GeneratedValue
    private int id;
    private String name;
    private String content;

    // Getters and Setters
}

As you can see above I have used @CreatedBy, @CreatedDate, @LastModifiedBy and @LastModifiedDate annotation on respective fields.

Spring Data JPA approach abstracts working with JPA callbacks and provides us these fancy annotations to automatically save and update auditing entities.

Using AuditingEntityListener class with @EntityListeners


Spring Data JPA provides a JPA entity listener class AuditingEntityListener which contains the callback methods (annotated with @PrePersist and @PreUpdate annotation) which will be used to persist and update these properties when we will persist or update our entity.

JPA provides the @EntityListeners annotation to specify callback listener classes which we use to register our AuditingEntityListener class.

However, We can also define our own callback listener classes if we want to and specify them using @EntityListeners annotation. In my next article, I will demonstrate how we can use @EntityListeners to store audit logs.

Auditing Author using AuditorAware and Spring Security


JPA can analyze createdDate and lastModifiedDate using current system time but what about the createdBy and lastModifiedBy fields, how JPA will recognize what to store in these fields?

To tell JPA about currently logged in user we will need to provide an implementation of AuditorAware and override getCurrentAuditor() method. And inside getCurrentAuditor() we will need to fetch currently logged in user.

As of now, I have provided a hard-coded user but you are using Spring security then you use it find currently logged in user same as I have mentioned in the comment. 

public class AuditorAwareImpl implements AuditorAware<String> {

    @Override
    public String getCurrentAuditor() {
        return "Naresh";
        // Can use Spring Security to return currently logged in user
        // return ((User) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername()
    }
}

Enable JPA Auditing by using @EnableJpaAuditing


We will need to create a bean of type AuditorAware and will also need to enable JPA auditing by specifying @EnableJpaAuditing on one of our configuration class. @EnableJpaAuditing accepts one argument auditorAwareRef where we need to pass the name of the AuditorAware bean.

@Configuration
@EnableJpaAuditing(auditorAwareRef = "auditorAware")
public class JpaConfig {
    @Bean
    public AuditorAware<String> auditorAware() {
        return new AuditorAwareImpl();
    }
}

And now if we will try to persist or update and file object CreatedBy, CreatedDate, LastModifiedBy, LastModifiedDate properties will automatically get saved.

In the next article JPA Auditing: Persisting Audit Logs Automatically using EntityListeners, I have discussed how we can use JPA EntityListeners to create audit logs and generate history records for every insert, update and delete operation.

You can find complete code on this Github Repository and please feel free to give your valuable feedback.
Next Post Newer Post Previous Post Older Post Home

48 comments :

  1. Hi Naresh, Great tutorial. You save my day. Thank You

    ReplyDelete
  2. So in your entity class, created_by is type U, but your AuditorAware return String, does spring automatically map user by userName string?

    ReplyDelete
    Replies
    1. I have defined `Auditable` class using generics and while extending it in `File` class I have passed `String` type in place of `U` as you can see in the code samples. So for `File` entity `U` becomes `String` and in `AuditorAwareImpl` we are fetching user name as Striung

      Delete
  3. great! work naresh! would it be possible for you do a in depth tutorial on spring session management??

    ReplyDelete
  4. Great information it exactly fit my use case. And saved a lot more manual effort

    ReplyDelete
  5. So while updating a record my created_by, created_date field are updating to null values. how can I fix it?

    ReplyDelete
    Replies
    1. This should not happen if you follow the article and the code, you can see the complete code at mentioned GitHub link and try replicate your scenario.

      Delete
  6. HI @Souma,

    good question. I found out why it's not working. In the Example above we take the full file data from the prevous file object, so the created_by, created_date fields are already set. BUT what we need is, that this two fields should not be written to the database if we just update the File object with other data. The listener works ok for the last_updated and for writing the history table, but the created_by, created_date will set to null, if we just set id, name and contend to a new File object...

    @Naresh
    Can you give suggestion how to solve it? Would be uncomfortable to read the data from the database first and then populate the fields for the update...

    Mean:
    File file1 = new File("Blaa","bluub");
    file1.setId(1); // this one i want to update in the DB
    fileRepository.save(file1);

    regards
    Patrick

    ReplyDelete
  7. finally this behavior is expected...
    Documentation say:
    @CreatedDate annotation. This identifies the field whose value is set when the entity is persisted to the database for the first time.

    MEans if you update the file later, then you need to fetch the date and user first, out inside the update Object and then persist it...

    regards
    Patrick

    ReplyDelete
  8. Hi Folks,
    here the solution:
    Solution:
    @Column(name="creationDate", updatable = false, nullable = false)

    cheers
    Patrick

    ReplyDelete
  9. Hi

    Nice work, I tried it with both MySQL and MS-SQL, it's working fine with MySQL but in MS-SQL only the user is getting saved and updated.

    ReplyDelete
    Replies
    1. Hi Arun, Generally this should not happen, do mind sharing your github link for your code so we can have a look.

      Delete
  10. Hi Naresh
    I used your code for JPA auditing.It works fine for hardcoded user(return Optional.of("Swati");) but i want to save currently logged in user(return Optional.of(((User)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername());) but I am facing some error.Please help

    ReplyDelete
    Replies
    1. @Swati, I have already used same code with `return ((User) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername()` in it and it works. I can not debug it without code, can you share your may be your git repo?

      Delete
  11. Hi Naresh,
    I just want to use @CreatedDate and @LastModifiedDate so is that possible to use these two annotation without implementing the AuditAware interface or any other way? If it is then please provide the solution.
    Thank you

    ReplyDelete
    Replies
    1. @Santosh I think in that case it should work without AuditAware but I have not tried it, maybe you can try it.

      Delete
    2. I tried it but its not working. Throwing null pointer. If you can try please do for me.

      Delete
    3. Hi Santosh, I just tested it and it works fine, I removed @CreatedBy and @ModifiedBy fields and then deleted AuditAware's implementation and then its bean creation logic from JpaConfig class and from @EnableJpaAuditing annotation.

      Delete
  12. Hi Naresh, I want to implement audit with mongo collections. When user will create, update, delete any entry in/from collection,
    I want it's previous state to get stored in another collection with same name with some identifier for collection as Audit for that collection. Like if my collection's name
    is "File" then I want another collection with name as "File_Aud" and I want this(File_Aud) collection to have all information of all modifications with some column as identifier to differentiate
    if it is "insert/update/delete" operation. We're using envers framework for such thing with MS_SQL but I'm not sure about MongoDB.

    ReplyDelete
    Replies
    1. I think any JPA or Hibernate auditing solution e.g. envers will not apply to mongodb but there will some ways to do that spring data mongodb, a simpe google search for `spring data mongodb auditing example` will give some examples.

      Delete