Introduction
The tutorial Spring Data JPA Entity Auditing using EntityListeners will show you how you persist the audit log using JPA’s built-in functionality. Spring Data JPA provides sophisticated support to transparently keep track of who created or changed an entity and at what time. To benefit from this functionality you have to equip your entity classes with auditing metadata that can be defined either using annotations or by implementing an interface.
Spring Data JPA provides @CreatedBy, @LastModifiedBy to capture the user who created or modified the entity as well as @CreatedDate and @LastModifiedDate to capture at what time this happened.
We will be using here annotation based auditing meta-data to implement the example on Spring Data JPA Entity Auditing using EntityListeners.
Prerequisites
Knowledge of Spring, Java
MySQL, Eclipse, JDK 1.8, Spring dependencies
Example with Source Code
Setting Up the Project in Eclipse
Create a gradle based project in Eclipse called spring-data-jpa-audit with the following build script.
In the below build script we are using Spring Boot version 2.0.3.RELEASE. You may use other version of Spring Boot as well to suite your requirements.
We have added repositories from where the required jars will be downloaded. We have added plugins to use for the project and also specified the compile and runtime Java version for the project.
We have added required dependencies for Web, Spring Data JPA and MySQL connector.
buildscript {
ext {
springBootVersion = '2.0.3.RELEASE';
}
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
allprojects {
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
compile("org.springframework.boot:spring-boot-starter-web:${springBootVersion}")
compile("org.springframework.boot:spring-boot-starter-data-jpa:${springBootVersion}")
compile("mysql:mysql-connector-java:8.0.11")
}
Building the Project
Once you are done with the above configurations then try to build the blank project using the gradle command from cmd prompt.
> gradle clean build
If you find any exception something like “Main class not found” then create a Java class with main method and try to build. You should see BUILD SUCCESSFUL message on the cmd prompt after downloading the required jar files.
Related Posts:
- Spring Data batch Insertion
- Spring EnableEncryptableProperties with Jasypt
- Hibernate UserType using Spring Data JPA
- Spring Data JPA Entity Graph
- Spring Data JPA CRUD Example
Creating application.properties
We need to create application.properties for any configurable variables such as datasource connection details, server port or any other configurations.
Put below properties file under src/main/resources directory.
The below file contains database credentials and server port on which we want to start the server instead of default port 8080 of Tomcat.
#datasource
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost/roytuts
spring.datasource.username=<username>
spring.datasource.password=<password>
server.port=9999
#disable schema generation from Hibernate
spring.jpa.hibernate.ddl-auto=none
AuditorAware Class
This class is required in order to insert or update value for fields created by and updated by into the database. The class should implement AuditorAware interface to provide the created by or updated by values.
To give an idea I am returning the value Admin but ideally you should use logged in user information to store the value for created by or updated by.
package com.roytuts.audit;
import java.util.Optional;
import org.springframework.data.domain.AuditorAware;
public class AuditorAwareImpl implements AuditorAware<String> {
@Override
public Optional<String> getCurrentAuditor() {
return Optional.of("Admin");
}
}
Application Config
This is basically configuration class that is used to configure different beans for one time activity.
For example, here we have defined DataSource, AuditorAware, EntityManagerfactory etc.
We have annotated the class with @EnableJpaAuditing in order to use the auditing functionality of Spring Data JPA.
We have also let Spring know in which package Spring Repository and Entity classes are kept.
package com.roytuts.audit.config;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
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.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import com.roytuts.audit.AuditorAwareImpl;
@Configuration
@EnableJpaAuditing(auditorAwareRef = "auditorProvider")
@EnableJpaRepositories(basePackages = "com.roytuts.audit.repository")
public class AppConfig {
@Autowired
private Environment environment;
@Bean
public AuditorAware<String> auditorProvider() {
return new AuditorAwareImpl();
}
@Bean
public DataSource dataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName(environment.getRequiredProperty("spring.datasource.driverClassName"));
ds.setUrl(environment.getRequiredProperty("spring.datasource.url"));
ds.setUsername(environment.getRequiredProperty("spring.datasource.username"));
ds.setPassword(environment.getRequiredProperty("spring.datasource.password"));
return ds;
}
@Bean
public EntityManagerFactory entityManagerFactory(DataSource dataSource) {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabase(Database.MYSQL);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("com.roytuts.audit.entity");
factory.setDataSource(dataSource);
factory.afterPropertiesSet();
return factory.getObject();
}
}
Creating MySQL Table
Finally, we store data into database table and for this we need to create a table in database for working on example Spring Data JPA Entity Auditing using EntityListeners.
We have created a table called employee with the below columns and four columns – created_by, created_date, updated_by, updated_date – will be inserted or updated through Spring Data JPA Auditing functionality.
CREATE TABLE `employee` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`email` varchar(150) NOT NULL,
`address` varchar(255) NOT NULL,
`created_by` varchar(50) NOT NULL,
`created_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`updated_by` varchar(50) DEFAULT NULL,
`updated_date` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=latin1;
Creating Entity Class
We have created below entity class to represent the database table row and each attribute represents the column in table.
Notice here we have used @CreatedBy, @CreatedDate, @LastModifiedBy and @LastModifiedDate to automatically insert the appropriate values into those columns into database table.
The @CreatedBy and @LastModifiedBy will be populated by AuditorAwareImpl class and @CreatedDate and @LastModifiedDate will be populated by current date time.
Note the @CreatedBy and @CreatedDate will be populated only once when the object or row is created first time, but @LastModifiedBy and @LastModifiedDate will be updated every time when an object or row gets modified. Remember if an object or row has no change then @LastModifiedBy and @LastModifiedDate will not be updated.
Notice how we have used @EntityListeners annotation for example on Spring Data JPA Entity Auditing using EntityListeners.
package com.roytuts.audit.entity;
import java.io.Serializable;
import java.time.LocalDateTime;
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 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;
@Entity
@Table(name = "employee")
@EntityListeners(AuditingEntityListener.class)
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer empId;
@Column(name = "name")
private String name;
@Column(name = "email")
private String email;
@Column(name = "address")
private String address;
@CreatedBy
@Column(name = "created_by")
private String createdBy;
@CreatedDate
@Column(name = "created_date")
private LocalDateTime createdDate;
@LastModifiedBy
@Column(name = "updated_by")
private String updatedBy;
@LastModifiedDate
@Column(name = "updated_date")
private LocalDateTime updatedDate;
//getters and setters
}
Creating Spring Data JPA Repository
This is Spring Data JPA Repository provided by Spring framework and it reduces our efforts significantly by providing built-in functionalities.
You can use built-in functions or you can just specify queries in the form of methods or using @Query annotation and Spring will generate appropriate SQL for you.
package com.roytuts.audit.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.roytuts.jpa.audit.entity.Employee;
public interface EmployeeJpaRepository extends JpaRepository<Employee, Integer> {
}
Creating Value Object
It is always good idea to create DTO or VO object instead of using entity object beyond data layer.
This VO object is equivalent to the entity object.
package com.roytuts.audit.vo;
public class EmployeeVo {
private Integer id;
private String name;
private String email;
private String address;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
Creating Service Class
In service layer code we always do our business or conversion from VO or DTO to entity or vice-versa.
In this class either we save the employee object or update the existing object. We also provide a method for retrieving all employee objects.
Here for saving new employee or updating the existing employee we use the same method in order to minimize the line of codes and there is only one difference that when you update the existing employee object or row in the table you have to pass additional attribute id value to identify the existing employee.
Notice in the example Spring Data JPA Entity Auditing using EntityListeners, we never set auditor values and those are taken care by Spring itself.
package com.roytuts.audit.service;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.roytuts.audit.entity.Employee;
import com.roytuts.audit.repository.EmployeeJpaRepository;
import com.roytuts.audit.vo.EmployeeVo;
@Service
public class EmployeeService {
@Autowired
private EmployeeJpaRepository employeeJpaRepository;
public void saveOrUpdateEmployee(EmployeeVo employee) {
Employee emp = null;
if (employee.getId() != null) {
emp = employeeJpaRepository.findByEmpId(employee.getId());
if (emp == null) {
return;
}
} else {
emp = new Employee();
}
emp.setName(employee.getName());
emp.setEmail(employee.getEmail());
emp.setAddress(employee.getAddress());
employeeJpaRepository.save(emp);
}
public List<EmployeeVo> getEmployees() {
List<Employee> employees = employeeJpaRepository.findAll();
return employees.stream().map(e -> {
EmployeeVo employeeVo = new EmployeeVo();
employeeVo.setId(e.getEmpId());
employeeVo.setName(e.getName());
employeeVo.setEmail(e.getEmail());
employeeVo.setAddress(e.getAddress());
return employeeVo;
}).collect(Collectors.toList());
}
}
Creating Spring REST Controller
This is Spring REST Controller class that handles request and response from clients.
We also specify the URI for REST resources. We specify the http method for each method with @GetMapping, @PostMapping etc. We need to also specify the arguments as @PathVariable or @RequestBody or @QueryParam etc.
Here we have used @RequestBody parameter for saving or updating the employee object because it would not be possible to send JSON data into query or path parameter.
We use the save method for saving or updating the employee object or row in the table because for updating the employee we need to send id value of the table row whereas for new employee the id value is created automatically using the Generation strategy (IDENTITY).
package com.roytuts.audit.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.roytuts.jpa.audit.service.EmployeeService;
import com.roytuts.jpa.audit.vo.EmployeeVo;
@RestController
public class EmployeeRestController {
@Autowired
private EmployeeService employeeService;
@PostMapping("/employee/save")
public ResponseEntity<Void> saveOrUpdateEmployee(@RequestBody EmployeeVo employee) {
employeeService.saveOrUpdateEmployee(employee);
return new ResponseEntity<>(HttpStatus.OK);
}
@GetMapping("/employees")
public ResponseEntity<List<EmployeeVo>> getEmployees() {
List<EmployeeVo> employees = employeeService.getEmployees();
return new ResponseEntity<>(employees, HttpStatus.OK);
}
}
Spring Boot Main Class
This main class starts up the embedded Tomcat server and deploys the application.
We have annotated the class with @SpringBootApplication in order to let Spring know that this application should be deployed in place and all required configurations should be done by Spring itself.
We have also put the base package so that any Spring bean that is annotated with @Component, @Controller, @RestController, @Service, @Repository etc. automatically picked up by Spring container from the base package or its children packages.
package com.roytuts.audit.app;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = "com.roytuts.audit")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Testing the Application
Now we will see how our example Spring Data JPA Entity Auditing using EntityListeners works by testing the application in the below process.
Saving Employee
Here is how we do testing for saving an employee object using REST client. Here we create a new employee with below information.

Now check the database table employee to verify whether created_by, created_date, updated_by and updated_date are populated with the appropriate values.
So from the below image it is clear that the values have been populated correctly into those two fields.
Therefore Spring Data JPA Entity Auditing using EntityListeners works as per the expectation.

Updating Employee
Here is how we do testing for updating an existing employee object using REST client.
Here we update the employee which is having an id 1 with below information.

Now check the database table employee to verify whether updated_date is updated with the appropriate values.
So from the below image it is clear that as we don’t have any change in the request data so updated_date has not been changed in employee table.

Now let’s try with different request data:

Now check the database table employee to verify whether updated_date is updated with the appropriate values.
So from the below image it is clear that updated_date has been changed in employee table with latest date time without updating created_date as expected.

That’s all. Hope you got an idea on Spring Data JPA Auditing using EntityListeners.
Related Posts:
- Spring Data batch Insertion
- Spring EnableEncryptableProperties with Jasypt
- Hibernate UserType using Spring Data JPA
- Spring Data JPA Entity Graph
- Spring Data JPA CRUD Example
Source Code
Thanks for reading.
Thank you so much for sharing !
Was looking for it and found exactly.