Spring Data JPA Entity Auditing using EntityListeners

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:

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.

spring data jpa entity auditing using entitylisteners

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.

spring data jpa entity auditing using entitylisteners

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.

spring data jpa entity auditing using entitylisteners

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.

spring data jpa entity auditing using entitylisteners

Now let’s try with different request data:

spring data jpa entity auditing using entitylisteners

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:

Source Code

download source code

Thanks for reading.

1 thought on “Spring Data JPA Entity Auditing using EntityListeners

Leave a Reply

Your email address will not be published. Required fields are marked *