엔티티를 생성, 변경할 때 변경한 사람과 시간을 추적하고 싶으면 언제 등록이 되었는지 언제 수정이 되었는지 두가지는 기본적으로 가져가야한다.
- 등록일
- 수정일
모든 테이블에 다 적용을 한 다는 것이고
추가적으로 넣어주는게 등록자 수정자에 관한 것이다.
로그인 한 세션 정보를 가지고 등록 액션을 하면 데이터를 넣을 때 등록자에 넣고
수정 액션을 하면 수정자에 넣고 업데이트를 하는 것이다.
- 등록자
- 수정자
먼저 순수 JPA를 사용해서 등록일, 수정일 문제를 해결해보자.
updatable = false를 넣어주어서 db의 값이 변경되지 못하게 하였다.
// JpaBaseEntity
package study.datajpa.entity;
import javax.persistence.Column;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import java.time.LocalDateTime;
public class JpaBaseEntity {
@Column(updatable = false)
private LocalDateTime createdDate;
private LocalDateTime updatedDate;
@PrePersist
public void prePersist() {
LocalDateTime now = LocalDateTime.now();
// 등록일과 수정일을 똑같이 맞춰놓는다.
createdDate = now;
updatedDate = now;
}
// Updatedate가 있으면 갱신
@PreUpdate
public void preUpdate() {
updatedDate = LocalDateTime.now();
}
}
Member Class에도 JpaBaseEntity를 extends 해준다.
// Member
@NamedEntityGraph(name = "Member.all", attributeNodes = @NamedAttributeNode("team"))
public class Member extends JpaBaseEntity{
속성들을 테이블에 내려서 데이터만 공유해주는 @MappedSuperclass를 JpaBaseEntity에 넣는다.
@MappedSuperclass
public class JpaBaseEntity {
@Column(updatable = false)
private LocalDateTime createdDate;
private LocalDateTime updatedDate;
실행을 시키면 아래 tiemstamp 두개가 추가 된것을 스키마에서 확인할 수 있고
create table member (
member_id bigint not null,
created_date timestamp,
updated_date timestamp,
age integer not null,
username varchar(255),
tema_id bigint,
primary key (member_id)
)
JpaBaseEntity에서 @Getter을 지정해준뒤
@Getter
@MappedSuperclass
public class JpaBaseEntity {
MemberTest를 설정해주자.
@Autowired
MemberRepository memberRepository;
@Test
public void JpaEventBaseEntity() throws Exception {
// given
Member member = new Member("member1");
memberRepository.save(member); //@PrePersist
// 시간이 지난 후 member2로 바꾼다.
Thread.sleep(100);
member.setUsername("member2");
em.flush(); // @PreUpdate
em.clear();
// when
Member findMember = memberRepository.findById(member.getId()).get();
// then
System.out.println("findMember.createdDate = " + findMember.getCreatedDate());
System.out.println("findMember.updatedDate = " + findMember.getUpdatedDate());
}
test를 돌려보면 현재 날짜가 나오게 된다.
findMember.createdDate = 2022-04-18T15:27:27.392305
findMember.updatedDate = 2022-04-18T15:27:27.524790
h2 db에 createdDate와 updateDate가 정확하게 잘 들어간것을 확인할 수 있다.
아래와 같이 다른 클래스에서도 entity 상속을 받을 수가 있다.
public class Team extends JpaBaseEntity{
JPA의 주요 이벤트 어노테이션은 아래와 같다.
- @PrePersist, @PostPersist
- @PreUpdate, @PostUpdate
스프링 데이터 JPA를 사용해보면
스프링 데이터 JPA 사용 설정
- @EnableJpaAuditing -> 스프링 부트 설정 클래스에 적용해야하고
- @EntityListeners(AuditingEntityListener.class) -> 엔티티에 적용
DataJpaApplication에서 아래와 같이 설정을 해주고
@EnableJpaAuditing
@SpringBootApplication
public class DataJpaApplication {
BaseEntity Class를 만든 다음에 @CreatedDate / @EntityListeners / @MappedSuperclass / @Getter을 넣는다.
package study.datajpa.entity;
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.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;
@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
@Getter
public class BaseEntity {
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdDate;
@LastModifiedDate
private LocalDateTime lastModifiedDate;
}
@CreatedDate / @LastModifiedDate를 보면 org.springframework인것을 알 수 있다.
package org.springframework.data.annotation;
MemberTest에서 아래와 같이 getUpdateDate를 getLastModifiedDate로 변경시킨 후에 실행히키면
@Test
public void JpaEventBaseEntity() throws Exception {
// given
Member member = new Member("member1");
memberRepository.save(member); //@PrePersist
// 시간이 지난 후 member2로 바꾼다.
Thread.sleep(100);
member.setUsername("member2");
em.flush(); // @PreUpdate
em.clear();
// when
Member findMember = memberRepository.findById(member.getId()).get();
// then
System.out.println("findMember.createdDate = " + findMember.getCreatedDate());
System.out.println("findMember.updatedDate = " + findMember.getLastModifiedDate());
}
같은 결과가 나오게 된다.
findMember.createdDate = 2022-04-18T15:40:09.635079
findMember.updatedDate = 2022-04-18T15:40:09.812216
등록자 수정자를 넣어보자
// BaseEntity
@CreatedBy
@Column(updatable = false)
private String createdBy;
@LastModifiedBy
private String lastModifiedBy;
DataJpaApplication을 아래와 같이 수정을 해주고
@EnableJpaAuditing
@SpringBootApplication
public class DataJpaApplication {
public static void main(String[] args) {
SpringApplication.run(DataJpaApplication.class, args);
}
@Bean
public AuditorAware<String> auditorProvider() {
return () -> Optional.of(UUID.randomUUID().toString());
}
}
MemberTest에 getCreatedBy와 getLastModifiedBy를 해서 돌려주면
package study.datajpa.entity;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;
import study.datajpa.repository.MemberRepository;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@Transactional
@Rollback(false)
class MemberTest {
@PersistenceContext
EntityManager em;
@Autowired
MemberRepository memberRepository;
@Test
public void testEntity() {
Team teamA = new Team("teamA");
Team teamB = new Team("teamB");
em.persist(teamA);
em.persist(teamB);
Member member1 = new Member("member1", 10, teamA);
Member member2 = new Member("member2", 20, teamA);
Member member3 = new Member("member3", 30, teamB);
Member member4 = new Member("member4", 40, teamB);
em.persist(member1);
em.persist(member2);
em.persist(member3);
em.persist(member4);
// 초기화
em.flush();
em.clear();
// 확인 =
List<Member> members = em.createQuery("select m from Member m", Member.class)
.getResultList();
for (Member member : members) {
System.out.println("member = " + member);
System.out.println("-> member.team = " + member.getTeam());
}
}
@Test
public void JpaEventBaseEntity() throws Exception {
// given
Member member = new Member("member1");
memberRepository.save(member); //@PrePersist
// 시간이 지난 후 member2로 바꾼다.
Thread.sleep(100);
member.setUsername("member2");
em.flush(); // @PreUpdate
em.clear();
// when
Member findMember = memberRepository.findById(member.getId()).get();
// then
System.out.println("findMember.createdDate = " + findMember.getCreatedDate());
System.out.println("findMember.updatedDate = " + findMember.getLastModifiedDate());
System.out.println("findMember.createdBy = " + findMember.getCreatedBy());
System.out.println("findMember.updatedBy = " + findMember.getLastModifiedBy());
}
}
UUID가 다른것을 확인할 수 있다.
findMember.createdDate = 2022-04-18T15:52:44.879581
findMember.updatedDate = 2022-04-18T15:52:45.025120
findMember.createdBy = 3e822b23-b418-4cce-9ab4-bd992e04cfd0
findMember.updatedBy = 9005d754-b955-4fb4-81f4-599f060ae8cc
그리고 만약에 BaseEntiy 안에다가 아래 구문을 다 넣기 귀찮다 그러면
@EntityListeners(AuditingEntityListener.class)
META-INF/orm.xml파일을 만들고 아래와 같이 설정을 해준 다음에 아래처럼 설정을 해주면 된다.
만약에 member에 등록일만 필요하다 그러면 member에 아래 구문만 넣어주면 된다.
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdDate;
테이블마다 특성이 다르므로 어떤 테이블은 등록일 수정일자만 필요하고 등록자 수정자가 필요가 없으면 BaseTimeEntity를 따로 만들고
package study.datajpa.entity;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import javax.persistence.Column;
import java.time.LocalDateTime;
public class BaseTimeEntity {
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdDate;
@LastModifiedDate
private LocalDateTime lastModifiedDate;
}
BaseEntity에서 BaseTimeEntity를 상속받으면 된다. 등록자 수정자는 테이블 마다 의미없는 테이블이 존재할 수도 있기 때문이다.
// BaseEntity
package study.datajpa.entity;
import lombok.Getter;
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;
import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;
@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
@Getter
public class BaseEntity extends BaseTimeEntity{
@CreatedBy
@Column(updatable = false)
private String createdBy;
@LastModifiedBy
private String lastModifiedBy;
}
이렇게 Auditing에 대해서 알아보았다.
<출처 김영한: 실전! 스프링 데이터 JPA >
실전! 스프링 데이터 JPA - 인프런 | 강의
스프링 데이터 JPA는 기존의 한계를 넘어 마치 마법처럼 리포지토리에 구현 클래스 없이 인터페이스만으로 개발을 완료할 수 있습니다. 그리고 반복 개발해온 기본 CRUD 기능도 모두 제공합니다.
www.inflearn.com