스프링/JPA

Entity를 변경/생성할 때, 변경자와 시간 히스토리 관리하려면?

nomoreFt 2022. 2. 24. 01:23

순수 JPA로 하는 법

@MappedSuperclass 를 선언한 속성값 선언용 Class를 만들어 Entity에서 상속받게 하라.

  • MappedSuperclass 생성, 변수 선언
@MappedSuperclass //실제 상속관계는 아니고 그냥 값만 내려서 사용할 수 있게 해주는 class
//이걸 선언해야 이후 상속한 Entity Table 생성시 변수값이 추가된다.
public class JpaBaseEntity {

    @Column(updatable = false) //DB의 값이 변경되지 않게 고정해주는 어노테이션
    private LocalDateTime createdDate;
    private LocalDateTime updatedDate;

    @PrePersist //persist (등록) 하기전에 발동
    public void prePersist(){
        LocalDateTime now = LocalDateTime.now();
        createdDate = now;
        updatedDate = now;
    }

    @PreUpdate //update 전에 발동
    public void preUpdate(){
        updatedDate = LocalDateTime.now();
    }

}

@MappedSuperclass
실제 Entity에서 상속하여 사용하지만, 그냥 변수 값만 내려 사용할 수 잇게 해주는 class이다.
이걸 선언해야 이후 상속한 Entity Table 생성시 변수값이 추가된다.

@Column(updatable = false)
본문에서는 createdDate가 최초 create시에만 들어가고 불변의 값이라 선언해줬다.
@PrePersist, PreUpdate Entity의 Update, Create 사전에 작동하는 메서드에 선언한다.

  • 생성일자가 등록되길 원하는 Entity에 상속
public class Member **extends JpaBaseEntity**{
}
  • 테스트

created_date, updated_date가 추가되어 생성되는 모습

create table member (
       member_id bigint not null,
        created_date timestamp,
        updated_date timestamp,
        age integer not null,
        username varchar(255),
        team_id bigint,
        primary key (member_id)
    )
    @Test
    public void mappedSuperClassTest() throws Exception {
        //given
        Member m1 = new Member("user1");
        memberRepository.save(m1); //@PrePersist 작동
        Thread.sleep(100);
        m1.setUsername("user2222");
        //when
        em.flush();
        em.clear();
        Member member = memberRepository.findById(m1.getId()).get();
        //then
        Assertions.assertThat(m1.getUpdatedDate()).isNotEqualTo(member.getUpdatedDate());
    }

SpringDataJpa

@EnableJpaAuditing 을 선언해서 Jpa의 Auditing 기능을 사용한다고 명시하고, auditorProvidor 메서드를 함께 명시해줘서
사용자, 수정자 등록시 가져갈 내용을 정의해준다.
이후 원하는 공통 속성 을 구분해서 class별로 선언해서 필요한 Entity에서 상속 받는다. (주로 시간과 나머지를 나눔)

  • Application@EnableJpaAuditing 선언

@EnableJpaAuditing Jpa의 Auditing 기능을 사용한다고 명시함.

@SpringBootApplication
**@EnableJpaAuditing** //JpaAuditing 기능을 사용한다고 명시함.
public class DataJpaApplication {

    public static void main(String[] args) {
        SpringApplication.run(DataJpaApplication.class, args);
    }

// 생성자, 수정자 등록시에 필요하다. JPA Auditing에서 고유 유저값 가져감
    @Bean
    public AuditorAware<String> **autitorProvidor()**{
        //보통 Spring Security나 Session 등을 가져와 고유값을 넣어준다.
        return () -> Optional.of(UUID.randomUUID().toString());
    }
}
  • 공통 속성 정의한 Class에 @EntityListeners, @MappedSuperclass , @CreatedBy, @LastModifiedBy 선언

@EntityListeners(AuditingEntityListener.class) 이벤트를 기반으로 동작한다고 선언하는 것.
@MappedSuperclass 공통속성용을 위해 상속용 class 선언
@CreatedBy 생성자 (Application.class에 선언된 auditorProvider에서 사용자 정보 가져가서 대입해준다.)
@LastModifiedBy 이하 동문


@EntityListeners(AuditingEntityListener.class) //이벤트를 기반으로 동작한다고 선언하는 것
@MappedSuperclass
@Getter
public class BaseEntity {
    //생성자, 수정자

    @CreatedBy
    @Column(updatable = false)
    private String createdBy;

    @LastModifiedBy
    private String lastModifiedBy;

}

실무 팁

공통 속셩별로 빼서 여러 class로 분산해서 필요에 따라 Entity에서 상속받아 적용하게 만든다.
너무 간단한 것들은 그냥 Entity 내부에 선언해주어도 무방하다.

  • Time속성 정의된 Class
@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
@Getter
public class BaseTimeEntity {

    @CreatedDate //org.springframework.data.annotation 데이터쪽 annotation이다.
    @Column(updatable = false)
    private LocalDateTime createdDate;

    @LastModifiedDate
    private LocalDateTime lastModifiedDate;
}
  • 생성자,수정자 속성 정의된 Class
@EntityListeners(AuditingEntityListener.class) //이벤트를 기반으로 동작한다고 선언하는 것
@MappedSuperclass
@Getter
public class BaseEntity {
    //생성자, 수정자

    @CreatedBy
    @Column(updatable = false)
    private String createdBy;

    @LastModifiedBy
    private String lastModifiedBy;
}