카테고리 없음

Auditing

느리지만 꾸준하게 2022. 4. 18. 16:03

엔티티를 생성, 변경할 때 변경한 사람과 시간을 추적하고 싶으면 언제 등록이 되었는지 언제 수정이 되었는지 두가지는 기본적으로 가져가야한다.

  • 등록일
  • 수정일

모든 테이블에 다 적용을 한 다는 것이고

추가적으로 넣어주는게 등록자 수정자에 관한 것이다.

로그인 한 세션 정보를 가지고 등록 액션을 하면 데이터를 넣을 때 등록자에 넣고

수정 액션을 하면 수정자에 넣고 업데이트를 하는 것이다.

  • 등록자
  • 수정자

먼저 순수 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 >

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%8D%B0%EC%9D%B4%ED%84%B0-JPA-%EC%8B%A4%EC%A0%84/dashboard

 

실전! 스프링 데이터 JPA - 인프런 | 강의

스프링 데이터 JPA는 기존의 한계를 넘어 마치 마법처럼 리포지토리에 구현 클래스 없이 인터페이스만으로 개발을 완료할 수 있습니다. 그리고 반복 개발해온 기본 CRUD 기능도 모두 제공합니다.

www.inflearn.com