스프링데이터 + JPA/스프링 데이터 JPA

21. Auditing

sdafdq 2023. 11. 24. 07:33

Auditing 직역은 감사하다. 감사를 수행하다.

 

이게 무슨 기능이냐면, 보통 테이블을 만들 때 등록일, 수정일 이 두개는 기본으로 깐다.

감사하다. 라는 뉘앙스 대로 뭔가 감찰하고, 뭐 그런건데,

저런 등록일이나 수정일, 더 나아가서는 등록자나 수정자까지 

 

자동으로 넣어주는 기능이다.

 

등록자 수정자는 어떤 관리자가 수정을 했고 취소를 했는지 등 시스템 관리자 id? 등으로 넣어줌.

 

실무에서 많이 사용함.

 

그럼 우선 jpa로 등록일, 수정일 적용

 

먼저 보통 이런 것들은 모든 테이블에 공통적으로 들어가는 사항이라,

@Getter
@MappedSuperclass
public class JpaBaseEntity {
    @Column(updatable = false)
    private LocalDateTime createDate;
    private LocalDateTime updatedDate;

    @PrePersist
    public void prePersist(){
        LocalDateTime now = LocalDateTime.now();
        createDate = now;
        updatedDate = now;
    }

    @PreUpdate
    public void preUpdate(){
        updatedDate = LocalDateTime.now();
    }
}

아예 이렇게 만들어 줌.

저 @MappedSuperclass가 뭐냐면,

superclass는 이제 부모 클래스를 보통 저렇게 말 하는데,

말 그대로 Mapped, 즉 얘가 다른 걸 가져와서 매핑시키는게 아니라 매핑 되어지는, 임.

 

https://qwefdg3.tistory.com/748

이거 보셈

 

기능 자체를 보자면 실체(테이블)가 아니라 속성을 상속받고 싶을 때

 

테이블 간의 상속관계는

https://qwefdg3.tistory.com/742

 

이거

 

둘이 다른거임. 하나는 속성만 상속받는, 즉 그냥 객체패러다임으로 엔티티의 특정부분을 공통화, 표준화 시킨거고,

아래 쪽은 진짜 DB세상에는 없는 테이블간의 상속관계에 대한 고민.

 

여튼 저걸 저렇게 @MappedSuperclass로 만들어서,

 

public class Member extends JpaBaseEntity{
    @Id @GeneratedValue
    @Column(name="member_id")
    private Long id;
    
    ....

이런 식으로 상속시켜 버리면,

 

create table member (
    age integer not null,
    create_date timestamp(6),
    member_id bigint not null,
    team_id bigint,
    updated_date timestamp(6),
    username varchar(255),
    primary key (member_id)
)

이렇게 테이블 만드는 쿼리가 나감.

 

그리고 당연히 객체간의 상속이니 member를 통해 부모 클래스 레벨의 데이터를 수정할 수 있음.

 

다음 볼거는,

@Getter
@MappedSuperclass
public class JpaBaseEntity {
    @Column(updatable = false)
    private LocalDateTime createDate;
    private LocalDateTime updatedDate;

    @PrePersist
    public void prePersist(){
        LocalDateTime now = LocalDateTime.now();
        createDate = now;
        updatedDate = now;
    }

    @PreUpdate
    public void preUpdate(){
        updatedDate = LocalDateTime.now();
    }
}

여기 @prePersist와 @preUpdate인데,

이거는 JPA가 제공해 주는 것으로, 예상 했듯이 persist하기 전에, update하기 전에 하고 싶은 일을 지정시킬 수 있음.

DB에서 트리거와 같은 역할임.

 

보면 persist 하기 전에 createDate와 updateDate에 값을 넣고 persist.

update도.

 

updatable = false는 생성일은 수정될 이유가 없으니까.

@Test
public void JpaEventBaseEntity(){
    Member member = new Member("member1", 10);

    memberRepository.save(member);

    em.flush();
    em.clear();
    Member findedMember = memberRepository.findById(member.getId()).get();

    findedMember.setUsername("changeTime");
}

이걸 실제로 이렇게 테스트 코드 짜 보면,

(저장했다가, flush, clear해서 쿼리 완전히 보내고 영속성 컨텍스트 비워준 다음에, 가져와서 데이터 바꿔본거임.(더티체킹을 통해))

@Test
public void JpaEventBaseEntity(){
    Member member = new Member("member1", 10);
    memberRepository.save(member);

    em.flush();

    member.setUsername("changeTime");
}

사실 이렇게 해도 되는데 flush 이후로 시간 조금이라도 더 보내게 하고 싶어서

 

여튼 해 보면

생성 날짜와 변경 날짜. 자동으로 들어감.

 

jpa 주요 이벤트 어노테이션은 이제 저거 말고도,

내가 DB의 트리거와 같다고 비유 했듯이

@PrePersist, @PostPersist

@PreUpdate, @PostUpdate

 

Delete, Load(select) 등등 있음.

 

 

이번엔 SpringDataJpa에서 제공해주는 기능을 이용해 볼 것이며, 누가 생성, 수정했는지도 넣어 볼거임.

 

이거 사용 하려면, @EnableJpaAuditing이라는 걸, JpaAuditing 사용 가능하게 하겠다 라는 걸 메인 애플리케이션 띄우는 곳에다 명시해 줘야 함.

@EnableJpaAuditing
@SpringBootApplication
public class DataJpaApplication {

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

이렇게.

 

@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
@Getter
public class BaseEntity {
    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdDate;

    @LastModifiedDate
    private LocalDateTime lastModifiedDate;

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

    @LastModifiedBy
    private String lastModifiedBy;
}

저 EntityListeners가 이벤트 등록시켜버리는 거임. 저건 Jpa꺼임. 근데 AuditingEntityListener, 즉 감사 엔티티 리스너 저 이벤트 종류 자체는 스프링 꺼임.

 

@CreateDate 이제 저 종류가 스프링 거임.

보면 알듯이 생성일 용 column, 필드로 만드는 거임. 우리가 jpa에서 했던 것들이 이제 저 필드로 들어감.

 

@LastModifiedDate 도 최근 수정(update)일

 

@CreateBy

저게 누구에 의해서 생성되었느냐, 인데,

이것도 메인 메소드에 등록을 해 줘야 함.

@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());
	}
}

아닌가 그냥 빈으로 등록시켜버리면 될 것 같기도 함.

 

보면 알겠지만 return 타입은 감사원 알아차리게 하는? 그런거고, 메소드명은 저거는 시스템 적으로 상관은 없긴 하지만 감사원 제공자. 

 

여튼 저건 콜백으로 줘야 하는데, 지금은 그냥 UUID로 줬는데 실제로 할 땐 세션에서 가지고 오고 그래야 한다고 함.

public class Member extends BaseEntity{
    @Id @GeneratedValue
    @Column(name="member_id")
    private Long id;
    .......

상속받게 하고

 

@Test
public void JpaEventBaseEntity(){
    Member member = new Member("member1", 10);

    memberRepository.save(member);

    em.flush();
    em.clear();
    Member findedMember = memberRepository.findById(member.getId()).get();

    findedMember.setUsername("changeTime");
}

그냥 똑같은 거 실행

 

잘 됨.

 

 

저 @EntityListeners(~~) 하는 거 넣기 귀찮으면 xml로 뭐 하는 방법이 있긴한데, 붙일 부분이 많이 있지 않아서 xml 만드는 게 더 귀찮을 듯.

 

 

근데 보통,

등록일 수정일은 거의 필수로 들어가는데,

등록자 수정자는 필요 없는 경우가 있다.

그럴때는,

@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
@Getter
public class BaseTimeEntity {
    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdDate;

    @LastModifiedDate
    private LocalDateTime lastModifiedDate;
}

이렇게 테이블에 거의 필수로 들어가는 BaseTimeEntity를 만들고,

 

@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
@Getter
public class BaseEntity extends BaseTimeEntity {

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

    @LastModifiedBy
    private String lastModifiedBy;
}

생성자, 수정자 엔티티를 만들어 BaseTimeEntity를 상속받게 한다.

 

둘 다 JPA상에서 이벤트 리스너가 필요하니 달아주고,

둘 다 속성 상속을 다른 것쪽으로 따라붙게 할 거니 @MappedSuperclass

 

이제 생성일, 수정일만 필요한 엔티티는 BaseTimeEntity만 쓰면 되고, 생성자, 수정자도 필요한 건 저걸 쓰면 된다.

 

개인적으로 Entity라는 이름이 맘이 안들긴 한데. 뭔가 속성이라든지 그런 이름이 더 좋지 않았을까..

BaseProperty? BaseEntityAttribute?