In my previous article Spring Data JPA Auditing: Saving CreatedBy, CreatedDate, LastModifiedBy, LastModifiedDate automatically, I have discussed why Auditing is important for any business application and how we can use Spring Data JPA automate it.

I have also discussed how Spring Data uses JPA’s EntityListeners and callback methods to automatically update CreatedBy, CreatedDate, LastModifiedBy, LastModifiedDate properties.

Well, here in this article I am going dig a little bit more and discuss how we can use JPA EntityListeners to create audit logs and keep information of every insert, update and delete operation on our data.

I will take the File entity example from the previous article and walk you through the necessary steps and code portions you will need to include in our project to automate the Auditing process.

We will use Spring Boot, Spring Data JPA (Because it gives us complete JPA functionality plus some nice customization by Spring), 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>

Implementing JPA Callback Methods using annotations @PrePersist, @PreUpdate, @PreRemove


JPA provides us the functionality to define callback methods for any entity using annotations @PrePersist, @PreUpdate, @PreRemove and these methods will get invoked before their respective life cycle event.

Similar to pre-annotations, JPA also provides post annotations like @PostPersist, @PostUpdate, @PostRemove, and @PostLoad. We can use them to define callback methods which will get triggered after the event.

JPA-Automatic-Auditing-Saving-Audit-Logs

Name of the annotation can tell you their respective event e.g @PrePersist - Before entity persists and @PostUpdate - After entity gets updated and this is same for other annotations as well.

Defining callback methods inside entity


We can define callback methods inside our entity class but we need to follow some rules like internal callback methods should always return void and take no argument. They can have any name and any access level and can also be static.

@Entity
public class File {

    @PrePersist
    public void prePersist() { // Persistence logic }

    @PreUpdate
    public void preUpdate() { //Updation logic }

    @PreRemove
    public void preRemove() { //Removal logic }

}

Defining callback methods in an external class and use @EntityListeners


We can also define our callback methods in an external listener class in a manner that they should always return void and accepts target object as the argument. However, they can have any name and any access level and can also be static.

public class FileEntityListener {
    @PrePersist
    public void prePersist(File target) { // Persistence logic }

    @PreUpdate
    public void preUpdate(File target) { //Updation logic }

    @PreRemove
    public void preRemove(File target) { //Removal logic }
}


And we will need to register this FileEntityListener class on File entity or its superclass by using @EntityListeners annotation

@Entity
@EntityListeners(FileEntityListener.class)
class File extends Auditable<String> {

    @Id
    @GeneratedValue
    private Integer id;
    private String name;
    private String content;

    // Fields, Getters and Setters
}

Advantages of using @EntityListeners


  • First of all, We should not write any kind of business logic in our entity classes and follow Single Responsibility Principle. Every entity class should be POJO (Plain Old Java Object).
  • We can have only one callback method for a particular event in a single class e.g. only one callback method with @PrePresist is allowed in a class. While we can define more than one listener class in @EntityListeners and every listener class can have a @PrePersist.

For example, I have used @EntityListeners on File and provided FileEntityListener class to it and I have also extended an Auditable class in File class.

The Auditable class itself have a @EntityListeners on it with AuditingEntityListener class because I am using this class to persist createdBy and other above-mentioned properties, You can check my previous article Spring Data JPA Auditing: Saving CreatedBy, CreatedDate, LastModifiedBy, LastModifiedDate automatically for more details.

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

    @CreatedBy
    protected U createdBy;

    @CreatedDate
    @Temporal(TIMESTAMP)
    protected Date createdDate;

    @LastModifiedBy
    protected U lastModifiedBy;

    @LastModifiedDate
    @Temporal(TIMESTAMP)
    protected Date lastModifiedDate;

    // Getters and Setters
}

We will also need to provide getters, setters, constructors, toString and equals methods to all the entities. However, you may like to look Project Lombok: The Boilerplate Code Extractor if you want to auto-generate these things.

Now we are all set and we need to implement our logging strategy, we can store history logs of the File in a separate history table FileHistory.

@Entity
@EntityListeners(AuditingEntityListener.class)
public class FileHistory {

    @Id
    @GeneratedValue
    private Integer id;

    @ManyToOne
    @JoinColumn(name = "file_id", foreignKey = @ForeignKey(name = "FK_file_history_file"))
    private File file;

    private String fileContent;

    @CreatedBy
    private String modifiedBy;

    @CreatedDate
    @Temporal(TIMESTAMP)
    private Date modifiedDate;

    @Enumerated(STRING)
    private Action action;

    public FileHistory() {
    }

    public FileHistory(File file, Action action) {
        this.file = file;
        this.fileContent = file.toString();
        this.action = action;
    }

    // Getters, Setters
}

Here Action is an enum

public enum Action {

    INSERTED("INSERTED"),
    UPDATED("UPDATED"),
    DELETED("DELETED");

    private final String name;

    private Action(String value) {
        this.name = value;
    }

    public String value() {
        return this.name;
    }

    @Override
    public String toString() {
        return name;
    }
}

And we will need to insert an entry in FileHistory for every insert, update, delete operation and we need to write that logic inside our FileEntityListener class. For this purpose, we will need to inject either repository class or EntityManager in FileEntityListener class.

Injecting Spring Managed Beans like EntityManager in EntityListeners


But here we have a problem, EntityListeners are instantiated by JPA not Spring, So Spring cannot inject any Spring-managed bean e.g. EntityManager in any EntityListeners.

So if you try to auto-wire EntityManager inside FileEntityListener class, it will not work

@Autowired EntityManager entityManager; //Will not work and entityManager will be null always

I have also written a separate article on how to AutoWire Spring Beans Into Classes Not Managed By Spring Like JPA Entity Listeners, you can read it if you want to know more.

And I am using the same idea here to make it work, we will create a utility class to fetch Spring managed beans for us

@Service
public class BeanUtil implements ApplicationContextAware {

    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }

    public static <T> T getBean(Class<T> beanClass) {
        return context.getBean(beanClass);
    }

}

And now we will write history record creation logic inside FileEntityListener

public class FileEntityListener {

    @PrePersist
    public void prePersist(File target) {
        perform(target, INSERTED);
    }

    @PreUpdate
    public void preUpdate(File target) {
        perform(target, UPDATED);
    }

    @PreRemove
    public void preRemove(File target) {
        perform(target, DELETED);
    }

    @Transactional(MANDATORY)
    private void perform(File target, Action action) {
        EntityManager entityManager = BeanUtil.getBean(EntityManager.class);
        entityManager.persist(new FileHistory(target, action));
    }

}

And now if we will try to persist or update and file object these auditing properties will automatically get saved.

You can find complete code on this Github Repository and please feel free to provide your valuable feedback.
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 Posts Previous Post Older Posts Home