JPA EntityListner example with Spring Boot 1.5.1

Page content

Introduction

To audit any transaction table you can use EntityListener Annotation of the JPA. JPA provide the EntityListener annotations that attach a listener class with the entity that you want to audit.

What is audit table ?

Audit Tables are used to track transactions against a particular table or tables. They allow you to see an ongoing “log” for future use.

There may be users and/or applications that have access to insert, update, and delete out of that table. If you may want to have a quick and easy way to track who is doing what on that table.

Scope

We will create a trigger like functionality. We shall audit all the DML operations (i.e. Insert, update and delete) on a table. We shall log all details of the operation, time-stamp, and the user name details in the audit log table.

Dependencies

Create Application

First create User Entity class that contain the user details data.

User.java

@Entity
public class User implements Serializable {
@Id
@Column(name = "id", nullable = false, updatable = false)
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@Column(name = "name")
private String name;
@Column(name = "city")
private String city;
public User(String name, String city) {
this.name = name;
this.city = city;
}
//Getter and Setter methods
}
view raw User.java hosted with ❤ by GitHub

Create Spring data repository to create CRUD operations on the database.

UserRepository.java

import com.example.entity.User;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends CrudRepository<User, Long> {
}

Create a Controller class to handle the Request -

UserController.java

package com.example.controller;
import com.example.entity.User;
import com.example.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.websocket.server.PathParam;
@RestController
public class UserController {
@Autowired
private UserRepository userRepository;
@RequestMapping(value="/", method = RequestMethod.GET)
public ResponseEntity<String> wellcome() {
return new ResponseEntity<String>("<h1>Welcome the this example.</h1>", HttpStatus.OK);
}
@RequestMapping(value = "/getusers", method = RequestMethod.GET)
public ResponseEntity<Iterable<User>> getUsers() {
return new ResponseEntity<>(userRepository.findAll(), HttpStatus.OK);
}
@RequestMapping(value="/save", method = RequestMethod.POST)
public ResponseEntity<User> save(@RequestBody User user) {
User userData = userRepository.save(user);
return new ResponseEntity<User>(userData, HttpStatus.OK);
}
@RequestMapping(value="/delete", method = RequestMethod.DELETE)
public ResponseEntity<Void> delete(@PathParam(value="id") long id) {
userRepository.delete(id);
return new ResponseEntity<>(HttpStatus.OK);
}
@RequestMapping(value="/update", method = RequestMethod.PUT)
public ResponseEntity<User> update(@RequestBody User user) {
return save(user);
}
}

Create Application class for spring boot configuration.

Application.java

package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

Project Structure -

Project Structure

Compile the application with below maven command -

mvn clean install

Run the example by below command on the command line -

mvn spring-boot:run

You can now perform create and update operation on the user entity.

Create AuditLog Entity and AuditLogRepository class

Create a AuditLog entity for audit log table, to keep the audit data -

AuditLog.java

package com.example.entity;
import javax.persistence.*;
import java.time.ZonedDateTime;
@Entity
public class AuditLog {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@Column(name="table_name")
private String tableName;
@Column(name="action")
private String action;
@Column(name="update_date")
private ZonedDateTime updateDate;
public AuditLog(String tableName, String action, ZonedDateTime updateDate) {
this.tableName = tableName;
this.action = action;
this.updateDate = updateDate;
}
public AuditLog() {
}
// Getter-Setter and toString() methods
}
view raw AuditLog.java hosted with ❤ by GitHub

Create a Spring repository for CRUD operation on the on database.

AuditLogRepository.java

package com.example.repository;
import com.example.entity.AuditLog;
import org.springframework.data.repository.CrudRepository;
public interface AuditLogRepository extends CrudRepository<AuditLog, Long> {
}

Attach AuditLog entity with User Entity class

First create a Entity Listener class that will listen the User table, and if any update operation performed on the User entity, the listener will call the call back methods.

UserAuditLogListener.java

package com.example.entity;
import com.example.BeanUtility;
import com.example.repository.AuditLogRepository;
import javax.persistence.PostPersist;
import javax.persistence.PostUpdate;
import javax.persistence.PreRemove;
import java.time.ZonedDateTime;
public class UserAuditLogListener {
@PostPersist
public void actionBeforeSave(final User user) {
System.out.println("Inside user Entity listener");
System.out.println(user.toString());
AuditLog auditLog = new AuditLog();
auditLog.setTableName("User");
auditLog.setAction("Add new user");
auditLog.setUpdateDate(ZonedDateTime.now());
AuditLogRepository auditLogRepository
= (AuditLogRepository) BeanUtility.getBean("auditLogRepository");
auditLogRepository.save(auditLog);
}
@PostUpdate
public void actionBeforeUpdate(final User user) {
System.out.println("Inside user Entity listener");
System.out.println(user.toString());
AuditLog auditLog = new AuditLog();
auditLog.setTableName("User");
auditLog.setAction("Update User : " + user.getName());
auditLog.setUpdateDate(ZonedDateTime.now());
AuditLogRepository auditLogRepository
= (AuditLogRepository) BeanUtility.getBean("auditLogRepository");
auditLogRepository.save(auditLog);
}
@PreRemove
public void actionBeforeDelete(final User user) {
System.out.println("Inside user Entity listener");
System.out.println(user.toString());
AuditLog auditLog = new AuditLog();
auditLog.setTableName("User");
auditLog.setAction("Delete User : " + user.getName());
auditLog.setUpdateDate(ZonedDateTime.now());
AuditLogRepository auditLogRepository
= (AuditLogRepository) BeanUtility.getBean("auditLogRepository");
auditLogRepository.save(auditLog);
}
}

The same callback method or entity listener method can be annotated with more than one callback annotation. For a given entity, you cannot have two methods being annotated by the same callback annotation whether it is a callback method or an entity listener method. A callback method is a no-arg method with no return type and any arbitrary name. An entity listener has the signature void (Object) where Object is of the actual entity type (note that Hibernate Entity Manager relaxed this constraint and allows Object of java.lang.Object type (allowing sharing of listeners across several entities.)

A callback method can raise a RuntimeException. The current transaction, if any, must be rolled back.

The following callbacks can be used -

TypeDescription
@PrePersistExecuted before the entity manager persist operation is actually executed or cascaded. This call is synchronous with the persist operation.
@PreRemoveExecuted before the entity manager remove operation is actually executed or cascaded. This call is synchronous with the remove operation.
@PostPersistExecuted after the entity manager persist operation is actually executed or cascaded. This call is invoked after the database INSERT is executed.
@PostRemoveExecuted after the entity manager remove operation is actually executed or cascaded. This call is synchronous with the remove operation.
@PreUpdateExecuted before the database UPDATE operation.
@PostUpdateExecuted after the database UPDATE operation.
@PostLoadExecuted after an entity has been loaded into the current persistence context or an entity has been refreshed.

For more information please refer this link.

Thing to keep in mind!! You can define several entity listeners per entity at different level of the hierarchy. You can also define several callbacks at different level of the hierarchy. But you cannot define two listeners for the same event in the same entity or the same entity listener.

When an event is raised, the listeners are executed in this order: @EntityListeners for a given entity or super class in the array order Entity listeners for the super classes (highest first) Entity Listeners for the entity Callbacks of the super classes (highest first) Callbacks of the entity

Modify User entity to attach EntityListener class

User.java

@Entity
@EntityListeners(value = UserAuditLogListener.class)
public class User implements Serializable {
...
...
}
view raw User.java hosted with ❤ by GitHub

Annotation @EntityListners specifies the callback listener classes to be used for an entity or mapped super class.@EntityListeners (value = UserAuditLogListener.class) will attach the UserAuditLogListener.class with the User Entity class.

JPA Entitylisteners are not entities, they are simply java classes in which JPA life cycle callback methods are implemented using annotations.

Save data in the AuditLog table

To inject AuditLogRepository into the UserAuditLogListener we can use an ApplicationContextAware Bean that will give the AuditRepository from Spirng context to save the Audit data. So, Create a BeanUtility service that implement the application context.

BeanUtility.java

package com.example;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
@Service
public class BeanUtility implements ApplicationContextAware {
private static ApplicationContext applicationContext;
public static Object getBean (String beanId) {
return applicationContext.getBean(beanId);
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}

Create a controller class to handle the request for audit log data .

AuditorController.java

package com.example.controller;
import com.example.entity.AuditLog;
import com.example.repository.AuditLogRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AuditorController {
@Autowired
private AuditLogRepository auditLogRepository;
@RequestMapping(value = "/getaudit" , method = RequestMethod.GET)
public ResponseEntity<Iterable<AuditLog>> getAudit() {
return new ResponseEntity<Iterable<AuditLog>>(auditLogRepository.findAll(), HttpStatus.OK);
}
}

Updated Project Structure

updated project structure

Now build the program using below maven command on the command line -

mvn clean install

Run the example by below command on the command line -

mvn spring-boot:run

You can download the sample code from below link - Sample Program