JPA Auditing
JPA Auditing이란?
JPA를 사용하여 Domain을 RDBS의 테이블에 매핑 할 때 공통적으로 Domain을 가지고 있는 필드나 컬럼이 존재한다.
대표적으로 다음과 같다.
- CreateDate
- UpdateDate
- 식별자
위와 같은 같은 필드 및 컬럼이 존재하는데, Domain마다 존재하게 된다면 중복 코드가 많이 발생하게 될 것이다. 이런 필드는 데이터베이스를 누가 언제 작성했는지 등 기록을 남긴 것이 유지 관리에도 도움이 된다. 따라서, 생성 날짜, 수정 날짜 같은 컬럼은 정말 중요한 데이터라고 볼 수 있다.
JPA에서는 그래서 Audit라는 기능을 제공한다. Audit은 감시한다는 의미에서 Spring Data JPA 시간에 자동으로 값을 넣어주는 기능이다. 도메인 지속성 컨텍스트에 저장하고 조회 등을 수행하고 update를 할 경우 매번 시간 데이터를 입력해야 하지만, audit을 이용하여 자동으로 시간을 매핑 데이터베이스의 테이블에 넣어준다.
프로젝트 생성
curl 명령어를 이용하여 spring-boot 프로젝트를 만든다. 이번에는 Dependencies에 “data-jpa,lombok,h2"을 포함한다.
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 -
혹은, 아래 URL를 브라우저에 복사해서 넣고 프로젝트를 생성한다.
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
소스 코드 구현
BaseTimeEntity 클래스
domain package 내에 BaseTimeEntity 클래스를 만들고, 아래와 같이 작성한다.
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 클래스는 모든 Entity 클래스의 부모 클래스이며, Entity 클래스의 createdDate
, modifiedDate
를 자동으로 관리하는 역할을 한다.
@MappedSuperclass
- JPA Entity 클래스가 BaseTimeEntity을 상속하는 경우 필드 (createdDate, modifiedDate)도 열로 인식되도록 한다.
@EntityListeners
(AuditingEntityListener.class)- BaseTimeEntity 클래스에 Auditing 기능을 제공한다.
@CreatedDate
- Entity가 생성되고 저장 될 때 시간이 자동으로 저장된다.
@LastModifiedDate
- 조회 한 Entity의 값을 변경할 때 시간이 자동으로 저장된다.
Entity 클래스에 BaseTimeEntity 클래스를 상속한다.
Post 클래스
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
- 테이블과 링크되는 클래스를 지정한다.
@Id
- PK 필드를 지정한다.
PostRepository 클래스
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 클래스
마지막으로 JPA Auditing 주석이 활성화되도록 main 클래스에 활성화 주석을 추가한다.
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
- JPA Auditing 주석이 활성화되도록 설정한다.
테스트
테스트는 필수적이므로, 테스트 코드도 작성해 보자.
PostRepositoryTest 클래스
테스크 코드를 test 폴더에 아래와 같이 작성한다.
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);
}
}
결과
... 이하 생략 ...
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'
... 이하 생략 ...
중간 부분에, createdDate, modifiedDate가 지금 시간으로 표시되는 것을 확인할 수 있다.
완성된 코드
위에 모두 완성된 프로젝트 코드는 여기에 있으니 참고 바란다.