Auditing helps us track and log the persistence layer changes made by the user in an application. By using auditing, we can determine who created or updated the entity record or when it happened. In short, we keep track of every action that changes the entity state, like insert, update, and delete operations.
Spring Data JPA provides excellent support to track who created or changed an entity and the time this happened.
To enable the auditing feature in Spring Boot, we can make use of Spring Data JPA's @CreateDate
, @CreatedBy
, @LastModifiedDate
, and @LastModifiedBy
annotations. You can add these annotations directly to your entity classes or by extending an abstract class that defines annotated audit fields.
Since we need an auditing feature for most entities, in this article, we will create a generic Auditable
abstract class with audit fields. Any entity can later extend this abstract class to enable the auditing functionality.
Dependencies
To use Spring Data JPA with the MySQL database in a Spring Boot application, spring-data-starter-data-jpa
and mysql-connector-java
dependencies are required.
Add the following dependencies to your Gradle project's build.gradle
file:
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'mysql:mysql-connector-java'
For Maven, include the following dependencies to your pom.xml
file:
<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>
If you are starting a new Spring Boot project, just use Spring Initializr web tool to bootstrap a new application with the above dependencies.
Configure MySQL Database
Spring Boot automatically configures the DataSource
bean for in-memory databases like H2 database, HSQLDB, and Apache Derby. For a MySQL database, you need to specify the database connection details in a properties file.
Open the application.properties
file and copy and paste the following properties:
# MySQL connection properties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=mypass
spring.datasource.url=jdbc:mysql://localhost:3306/testdb?createDatabaseIfNotExist=true&useSSL=false
# Log JPA queries
# Comment this in production
spring.jpa.show-sql=true
# Drop and create new tables (create, create-drop, validate, update)
# Only for testing purposes - comment this in production
spring.jpa.hibernate.ddl-auto=create
# Hibernate SQL dialect
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
Spring Boot will automatically configure DataSource
based on the above properties. Make sure you change the spring.datasource.username
and spring.datasource.password
properties per your MySQL database installation.
The hibernate property spring.jpa.hibernate.ddl-auto = create
will automatically create database tables based on the entity classes when the application starts.
The createDatabaseIfNotExist=true
configuration property, included in spring.datasource.url
, automatically creates the database schema if it doesn't already exist.
Create Auditable
Abstract Class
Let us now create an abstract Auditable
class with the createdBy
, createdDate
, lastModifiedBy
, and lastModifiedDate
properties. This generic class acts as a base class with all the common auditing fields for the child entities.
To let the Spring Boot know about these audit fields, you have to annotate the fields with @CreatedBy
and @LastModifiedBy
to track the user who created or updated the entity, @CreatedDate
and @LastModifiedDate
to log the time when these changes were made.
Here is what our Auditable
abstract class looks like:
Auditable.java
package com.attacomsian.jpa.domains;
import org.springframework.data.annotation.*;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import java.util.Date;
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class Auditable<T> {
@CreatedBy
protected T createdBy;
@Temporal(TemporalType.TIMESTAMP)
@CreatedDate
protected Date createdDate;
@LastModifiedBy
protected T lastModifiedBy;
@Temporal(TemporalType.TIMESTAMP)
@LastModifiedDate
protected Date lastModifiedDate;
public T getCreatedBy() {
return createdBy;
}
public void setCreatedBy(T createdBy) {
this.createdBy = createdBy;
}
public Date getCreatedDate() {
return createdDate;
}
public void setCreatedDate(Date createdDate) {
this.createdDate = createdDate;
}
public T getLastModifiedBy() {
return lastModifiedBy;
}
public void setLastModifiedBy(T lastModifiedBy) {
this.lastModifiedBy = lastModifiedBy;
}
public Date getLastModifiedDate() {
return lastModifiedDate;
}
public void setLastModifiedDate(Date lastModifiedDate) {
this.lastModifiedDate = lastModifiedDate;
}
}
As you can see above, the Auditable
class is also annotated with the @MappedSuperclass
and @EntityListeners
annotations. The @MappedSuperclass
annotation indicates that the Auditable
is only a superclass and is not a JPA entity.
The @EntityListeners
annotation is used to configure a JPA entity listener AuditingEntityListener
to capture auditing information on persisting and updating entities. This entity listener class contains callback methods (annotated with @PrePersist
and @PreUpdate
) to persist and update audit fields when there is any create or update activity on the entity.
Any entity that extends the Auditable
abstract class will benefit from the JPA auditing feature. Spring Data JPA will automatically manage the CreatedBy
, CreatedDate
, LastModifiedBy
, and LastModifiedDate
columns when the entity is persisted or updated.
Create an Entity
The next step is to create a Todo
entity class and then extend it from the Auditable
abstract class to add the auditing functionality:
Todo.java
package com.attacomsian.jpa.domains;
import javax.persistence.*;
import java.io.Serializable;
@Entity
@Table(name = "todos")
public class Todo extends Auditable<String> implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private boolean completed;
public Todo() {
}
public Todo(String title, boolean completed) {
this.title = title;
this.completed = completed;
}
// getters and setters, equals(), toString() .... (omitted for brevity)
}
The Todo
class is annotated with the Entity
annotation to indicate that it is a JPA entity. The @Table
annotation is used to specify the name of the database table that should be mapped to this entity.
The id
attribute is annotated with both @Id
and @GeneratedValue
annotations. The former indicates that it is a primary key of the entity. The latter defines the primary key generation strategy. In the above case, we have declared the primary key as an AUTO INCREMENT
field.
Auditing Author with AuditorAware
We use the @CreatedDate
and @LastModifiedDate
annotations for tracking created and last modified dates. Spring Data JPA automatically updates these fields by taking the current system time.
But how to tell the auditing infrastructure about the author who made these changes? It needs to know this information since we have defined the @CreatedBy
and @LastModifiedBy
annotations in our Auditable
abstract class.
To tell the auditing infrastructure about the currently logged-in user, we have to provide the implementation of AuditorAware
and override its getCurrentAuditor
method, as shown below:
EntityAuditorAware.java
package com.attacomsian.jpa.domains;
import org.springframework.data.domain.AuditorAware;
import java.util.Optional;
public class EntityAuditorAware implements AuditorAware<String> {
@Override
public Optional<String> getCurrentAuditor() {
return Optional.of("Atta");
}
}
Note: For the sake of simplicity, I am returning a hard-coded auditor name in the
getCurrentAuditor()
method. For real-world applications with Spring Security, you need to find the current logged-in user and return it back.
Create a Repository
Let us now create the TodoRepository
interface to save and retrieve Todo
entities from the database as follows:
TodoRepository.java
package com.attacomsian.jpa.repositories;
import com.attacomsian.jpa.domains.Todo;
import org.springframework.data.repository.CrudRepository;
public interface TodoRepository extends CrudRepository<Todo, Long> {
// TODO: add queries
}
We're extending TodoRepository
from Spring Data JPA's CrudRepository interface to inherit the standard CRUD methods for creating, reading, updating, and deleting Todo
entities.
Enable JPA Auditing
Finally, we need to enable the JPA auditing feature by specifying @EnableJpaAuditing
on one of our configuration classes. We also need to define a bean of type AuditorAware
and return an instance of the EntityAuditorAware
class.
You can create a separate configuration class or use the main application class to define these configurations. Let us create the AuditConfiguration
class to let the Spring Data JPA knows we want to enable auditing:
AuditConfiguration.java
package com.attacomsian.jpa.config;
import com.attacomsian.jpa.domains.EntityAuditorAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@Configuration
@EnableJpaAuditing
public class AuditConfiguration {
@Bean
public AuditorAware<String> auditorAware() {
return new EntityAuditorAware();
}
}
That's all you need to do to enable Spring Data JPA auditing functionality in your Spring Boot and MySQL application. Let us now create the main application class to test our implementation.
Testing the Application
It is time to create the main application class for our Spring Boot project. This class also exposes a bean of type CommandLineRunner
that defines a run()
method, which is invoked by Spring Boot after the application context has been loaded.
Application.java
package com.attacomsian.jpa;
import com.attacomsian.jpa.domains.Todo;
import com.attacomsian.jpa.repositories.TodoRepository;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import java.util.Arrays;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public CommandLineRunner auditingDemo(TodoRepository todoRepository) {
return args -> {
// create new todos
todoRepository.saveAll(Arrays.asList(
new Todo("Buy milk", false),
new Todo("Email John", false),
new Todo("Visit Emma", false),
new Todo("Call dad", true),
new Todo("Weekend walk", true),
new Todo("Write Auditing Tutorial", true)
));
// retrieve all todos
Iterable<Todo> todos = todoRepository.findAll();
// print all todos
todos.forEach(System.out::println);
};
}
}
As you can see above, we first saved several todos and then retrieved them using the findAll()
method. The last statement prints all todos on the console.
To see the actual output, you need to run the application. If you are using Gradle, execute the following command to start the application:
$ ./gradlew bootRun
For Maven, type the following command to launch the application:
$ ./mvnw spring-boot:run
Once the application is started, you should see the following output:
Todo{id=1, title='Buy milk', completed=false, createdBy=Atta, createdDate=2019-10-21 03:01:25.0, lastModifiedBy=Atta, lastModifiedDate=2019-10-21 03:01:25.0}
Todo{id=2, title='Email John', completed=false, createdBy=Atta, createdDate=2019-10-21 03:01:25.0, lastModifiedBy=Atta, lastModifiedDate=2019-10-21 03:01:25.0}
Todo{id=3, title='Visit Emma', completed=false, createdBy=Atta, createdDate=2019-10-21 03:01:25.0, lastModifiedBy=Atta, lastModifiedDate=2019-10-21 03:01:25.0}
Todo{id=4, title='Call dad', completed=true, createdBy=Atta, createdDate=2019-10-21 03:01:25.0, lastModifiedBy=Atta, lastModifiedDate=2019-10-21 03:01:25.0}
Todo{id=5, title='Weekend walk', completed=true, createdBy=Atta, createdDate=2019-10-21 03:01:25.0, lastModifiedBy=Atta, lastModifiedDate=2019-10-21 03:01:25.0}
Todo{id=6, title='Write Auditing Tutorial', completed=true, createdBy=Atta, createdDate=2019-10-21 03:01:25.0, lastModifiedBy=Atta, lastModifiedDate=2019-10-21 03:01:25.0}
Source Code: Download the complete source code from GitHub available under MIT license.
Conclusion
In this article, you have learned about Spring Data JPA auditing and how to enable it in a Spring Boot and MySQL application.
In short, all you need to do is the following to enable the JPA auditing feature:
- Define the audit fields using the
@CreatedDate
,@CreatedBy
,@LastModifiedDate
, and@LastModfiiedBy
annotations. The best way to do so is by creating a generic abstract class and extending the entities that need the auditing functionality. - Implement the
AuditorAware
interface to let Spring Data JPA auditing infrastructure know about the currently logged-in user making the changes. - Add the
@EnableJpaAuditing
annotation to any configuration class to enable JPA auditing. - Expose a bean of type
AuditorAware
(only required if you need an auditing author).
Further Reading
To learn more about Spring Data JPA, check out the following articles:
- Getting Started with Spring Data JPA
- Spring Data JPA with H2 DataBase and Spring Boot
- Accessing Data with Spring Data JPA and MySQL
- Derived Query Methods in Spring Data JPA
- Spring Data JPA Custom Queries using @Query Annotation
- How to Use Spring Data JPA Named Queries
- Sorting Query Results in Spring Data JPA
- Pagination with Spring Data JPA
- Spring Data JPA One To One Relationship Mapping Example
- Spring Data JPA One To Many Relationship Mapping Example
- Spring Data JPA Many To Many Relationship Mapping Example
- Spring Data JPA Composite Primary Key Mapping Example
- Introduction to Spring Data JPA Repositories
- Dynamic Queries with Spring Data JPA Specifications
✌️ Like this article? Follow me on Twitter and LinkedIn. You can also subscribe to RSS Feed.