JPA Auditing
What Is JPA Auditing?
When mapping domains to RDBMS tables with JPA, many domains have common fields or columns, such as:
- Create date
- Update date
- Identifier
Repeating these fields in every domain causes duplicated code. Creation and modification dates are also important data for maintenance because they provide a record of when data was written or changed.
JPA provides auditing support to populate this information automatically. Instead of setting timestamps every time an entity is stored or updated in the persistence context, auditing maps them to database columns automatically.
Create a Project
Create a Spring Boot project with data-jpa, lombok, and h2 dependencies:
curl https://start.spring.io/starter.tgz \
-d bootVersion=2.5.0 \
-d dependencies=data-jpa,lombok,h2 \
-d baseDir=spring-jpa-auditing \
-d artifactId=jpa-auditing \
-d packageName=com.devkuma.jpa.auditing \
-d applicationName=Application \
-d packaging=jar \
-d javaVersion=1.8 \
-d type=gradle-project | tar -xzvf -
Alternatively, open the following URL in a browser:
https://start.spring.io/#!type=gradle-project&language=java&platformVersion=2.5.0.RELEASE&packaging=jar&javaVersion=11&groupId=com.devkuma&artifactId=spring-jpa-auditing&name=spring-jpa-auditing&description=Demo%20project%20for%20Spring%20Boot&packageName=com.devkuma.jpa.auditing&dependencies=data-jpa,lombok,h2
Implement the Source Code
BaseTimeEntity class
Create BaseTimeEntity in the domain package:
package com.devkuma.jpa.auditing.domain;
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseTimeEntity {
@CreatedDate
private LocalDateTime createdDate;
@LastModifiedDate
private LocalDateTime modifiedDate;
}
BaseTimeEntity is a parent class for entities and automatically manages their createdDate and modifiedDate values.
@MappedSuperclass: Treats inherited fields as columns in JPA entities.@EntityListeners(AuditingEntityListener.class): Enables auditing behavior for the class.@CreatedDate: Stores the time automatically when an entity is created and saved.@LastModifiedDate: Stores the time automatically when an entity is updated.
Post class
Extend BaseTimeEntity from an entity:
package com.devkuma.jpa.auditing.domain;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Getter
@NoArgsConstructor
@Entity
public class Post extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(length = 500, nullable = false)
private String title;
@Column(columnDefinition = "TEXT", nullable = false)
private String content;
private String author;
@Builder
public Post(String title, String content, String author) {
this.title = title;
this.content = content;
this.author = author;
}
public void update(String title, String content) {
this.title = title;
this.content = content;
}
}
@Entity: Marks a class linked to a table.@Id: Marks the primary key field.
PostRepository class
Create a JPA repository:
package com.devkuma.jpa.auditing.repository;
import com.devkuma.jpa.auditing.domain.Post;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PostRepository extends JpaRepository<Post, String> {
}
Application class
Add @EnableJpaAuditing to the main class:
package com.devkuma.jpa.auditing;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@EnableJpaAuditing
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@EnableJpaAuditing: Activates JPA auditing annotations.
Test
Create the following test under the test source folder:
package com.devkuma.jpa.auditing;
import com.devkuma.jpa.auditing.domain.Post;
import com.devkuma.jpa.auditing.repository.PostRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.LocalDateTime;
import java.util.List;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
@SpringBootTest
public class PostRepositoryTest {
@Autowired
PostRepository postRepository;
@Test
public void 게시글저장() {
// given
LocalDateTime now = LocalDateTime.of(2020, 8, 12, 0, 0, 0);
postRepository.save(Post.builder()
.title("title")
.content("content")
.author("author")
.build());
// when
List<Post> postsList = postRepository.findAll();
//then
Post posts = postsList.get(0);
System.out.println(">>>>>>> createdDate=" + posts.getCreatedDate() + ", modifiedDate=" + posts.getModifiedDate());
assertThat(posts.getCreatedDate()).isAfter(now);
assertThat(posts.getModifiedDate()).isAfter(now);
}
}
Result
... omitted ...
2021-06-01 09:51:33.635 INFO 71886 --- [ Test worker] c.d.jpa.auditing.PostRepositoryTest : Started PostRepositoryTest in 7.572 seconds (JVM running for 8.631)
>>>>>>> createdDate=2021-06-01T09:51:33.762582, modifiedDate=2021-06-01T09:51:33.762582
2021-06-01 09:51:34.006 INFO 71886 --- [extShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
... omitted ...
The output confirms that createdDate and modifiedDate contain the current time.
Complete Code
The complete project is available here.