In the tutorial, we show how to build a SpringBoot application with a Database Auditing feature for tracking the change (with 2 fields created_at
& updated_at
) by using Spring Data JPA, Java 8 DateTime, and MySQL.
Related posts:
– How to use Spring JPA MySQL | Spring Boot
– Spring JPA Hibernate One to Many Relationship – SpringBoot + MySQL
Technologies
– Java 8
– SpringBoot 2.0.6.RELEASE
– MySQL
Goal
We create a SpringBoot project as below structure:
Run & Check results:
-> Post request:
-> Put request:
With 2 fields created_at
& updated_at
, we can track the change of database’s records.
How to achieve it? -> We use Spring JPA Auditing.
– Firstly, configure JPA Auditing:
@Configuration @EnableJpaAuditing public class JpaAuditingConfig {}
– Then we create a DateAudit
class as below:
@MappedSuperclass @EntityListeners(AuditingEntityListener.class) @JsonIgnoreProperties( value = {"createdAt", "updatedAt"}, allowGetters = true ) public abstract class DateAudit { @CreatedDate @Column(nullable = false, updatable = false) private Instant createdAt; @LastModifiedDate @Column(nullable = false) private Instant updatedAt; // getters & setters // ...
– Domain class Customer
need inherit from DateAudit
class:
@Entity @Table(name = "customers") public class Customer extends DateAudit { // ...
Pratice
Create SpringBoot project
– We create a SpringBoot project with below dependencies:
org.springframework.boot spring-boot-starter-data-jpa org.springframework.boot spring-boot-starter-web mysql mysql-connector-java com.fasterxml.jackson.datatype jackson-datatype-jsr310
– jackson-datatype-jsr310
is used to support Java 8 Date/Time.
JPA Audit Implementation
– Configure JPA Auditing:
package com.ozenero.jpaaudit.config; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @Configuration @EnableJpaAuditing public class JpaAuditingConfig {}
– Implement DateAudit
->
package com.ozenero.jpaaudit.model.audit; import java.time.Instant; import javax.persistence.Column; import javax.persistence.EntityListeners; import javax.persistence.MappedSuperclass; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @MappedSuperclass @EntityListeners(AuditingEntityListener.class) @JsonIgnoreProperties( value = {"createdAt", "updatedAt"}, allowGetters = true ) public abstract class DateAudit { @CreatedDate @Column(nullable = false, updatable = false) private Instant createdAt; @LastModifiedDate @Column(nullable = false) private Instant updatedAt; public Instant getCreatedAt() { return createdAt; } public void setCreatedAt(Instant createdAt) { this.createdAt = createdAt; } public Instant getUpdatedAt() { return updatedAt; } public void setUpdatedAt(Instant updatedAt) { this.updatedAt = updatedAt; } }
– @EntityListeners
annotation is used to specify callback listener classes. We use the JPA entity listener class of Spring Data AuditingEntityListener
.
– @MappedSuperClass
annotation is used to move the properties to a base class DateAudit
which would be extended by all your audited entities.
Create Model class
– Create JsonView Customer class:
package com.ozenero.jpaaudit.model.view; public class View { public static interface Customer {} }
– Create Customer
model class:
package com.ozenero.jpaaudit.model; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; import com.fasterxml.jackson.annotation.JsonView; import com.ozenero.jpaaudit.model.audit.DateAudit; import com.ozenero.jpaaudit.model.view.View; @Entity @Table(name = "customers") public class Customer extends DateAudit { @JsonView(View.Customer.class) @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @JsonView(View.Customer.class) @Column(name = "name") private String name; @JsonView(View.Customer.class) @Column(name = "address") private String address; @JsonView(View.Customer.class) @Column(name = "age") private int age; public Customer() { } public Customer(Long id, String name, String address, int age) { this.id = id; this.name = name; this.address = address; this.age = age; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Customer [id=" + id + ", name=" + name + ", address=" + address + ", age=" + age + "]"; } }
@JsonView
annotation of Jackson library is used to serialize/de-serialize Java objects and customize the fields of JSON view.
-> See more at: How to use @JsonView to serialize/de-serialize and customize JSON format from Java Object
Create JPA Repository
– CustomerRepository.java
->
package com.ozenero.jpaaudit.repository; import org.springframework.data.jpa.repository.JpaRepository; import com.ozenero.jpaaudit.model.Customer; public interface CustomerRepository extends JpaRepository{ }
RestAPIs Controller Implementation
– CustomerRestAPIs.java
->
package com.ozenero.jpaaudit.controller; import java.util.List; import javax.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.fasterxml.jackson.annotation.JsonView; import com.ozenero.jpaaudit.model.Customer; import com.ozenero.jpaaudit.model.view.View; import com.ozenero.jpaaudit.repository.CustomerRepository; @RestController @RequestMapping("/api/customers") public class CustomerRestAPIs { @Autowired CustomerRepository repository; @PostMapping public Customer saveCustomer(@Valid @RequestBody Customer customer) { return repository.save(customer); } @PutMapping public Customer updatedCustomer(@Valid @RequestBody Customer updatedCustomer) { return repository.findById(updatedCustomer.getId()) .map(customer -> { customer.setName(updatedCustomer.getName()); customer.setAddress(updatedCustomer.getAddress()); customer.setAge(updatedCustomer.getAge()); return repository.save(customer); }).orElseThrow(() -> new RuntimeException ("Customer not found with id " + updatedCustomer.getId())); } @GetMapping @JsonView(View.Customer.class) public ListgetAll(){ return repository.findAll(); } }
Main Class Implementation
– Set the default timezone for our application to UTC
.
package com.ozenero.jpaaudit; import java.util.TimeZone; import javax.annotation.PostConstruct; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.data.jpa.convert.threeten.Jsr310JpaConverters; @SpringBootApplication @EntityScan(basePackageClasses = { SpringBootJpaAuditApplication.class, Jsr310JpaConverters.class }) public class SpringBootJpaAuditApplication { @PostConstruct void init() { TimeZone.setDefault(TimeZone.getTimeZone("UTC")); } public static void main(String[] args) { SpringApplication.run(SpringBootJpaAuditApplication.class, args); } }
Application Configuration
– application.properties
->
spring.datasource.url=jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=UTC&useLegacyDatetimeCode=false spring.datasource.username=root spring.datasource.password=12345 spring.jpa.generate-ddl=true spring.jpa.hibernate.ddl-auto=create-drop ## Hibernate Logging logging.level.org.hibernate.SQL= DEBUG ## Jackson Properties spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS= false spring.jackson.time-zone= UTC
Run & Check results
Start SpringBoot project,
-> MySQL table:
– Post customers ->
– Update customer ->
– Get customers ->