본문 바로가기
Spring/Spring JPA

Auditing을 이용한 엔티티 공통 속성 공통화

by chogigang 2023. 3. 6.

실제 서비스를 운영할 때는 보통 등로시간과 수정시간,등록자,수정자 를 테이블에 넣어 놓고 활용을 합니다.

그리고 데이터가 생성되거나 수정될 때 시간을 기록해주고, 어떤 사용자가 등록을 했는지 아이디를 남깁니다.

이 컬럼들은 버그가 있거나 문의가 들어왔을 때 활용이 가능합니다. 데이터를 대용량으로 데이터를 업데이트했는데,

다시 업데이트를 해야 할 경우 변경딘 대상을 찾을 때 활용할 수도 있습니다.

 

Spring Data Jpa에서는 Auditing 기능을 제공하여 엔티티가 저장 또는 수정될 때 자동으로 등록일,수정일,등록자,수정자를 입력해줍니다. Audit의 사전적 정의는 '감사하다' 입니다. 즉 , 엔티티의 생명성과 수정을 감시하고 있는 것입니다. 이런 공통 맴버 변수들을 추상클래스로 마들고, 해당 추상 클래스를 상속받는 형태로 엔티티를 리팩토링 하겠습니다.

 

Auditing 기능을 활용한 데이터 추적하기

 

AuditorAwarelmpl

package com.shop.confing;


import org.springframework.data.domain.AuditorAware;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;

import java.util.Optional;

public class AuditorAwareImpl implements AuditorAware<String> {

    @Override
    public Optional<String> getCurrentAuditor(){
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        String userId = "";
        if (authentication !=null){
            userId = authentication.getName();
        }
        return Optional.of(userId);
    }
}

1.현재 로그인 한 사용자의 정보를 조회하여 사용자의 이름을 등록자와 수정자로 지정합니다.

 

Auditing 기능을 사용하기 위해서 Confing 파일을 생성하겠습니다.

 

AuditConfing

package com.shop.confing;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@Configuration
@EnableJpaAuditing
public class AuditConfig {

    @Bean
    public AuditorAware<String> auditorProvider(){
        return new AuditorAwareImpl();
    }
}
  1. JPA의 Auditing 기능을 활서화합니다.
  2. 등록자와 수정자를 처리해주는 AuditorAware을 빈으로 등록합니다.

 

보통 테이블에 등록일,수정일,등록자,수정자를 모두 다 넣어주지만 어떤 테이블을 등록자,수정자를 넣지 않는 테이블도 있을 수 있습니다. 그런 엔티티는 BaseTimeEntity만 상속받을 수 있도록 BaseTimeEntity 클래스를 생성합니다.

 

BaseTimeEntity

package com.shop.entity;

import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.CreatedDate;
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(value = {AuditingEntityListener.class})
@MappedSuperclass
@Getter
@Setter
public class BaseTimeEntity {
    
    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime updateTime;
}

 

  1. Auditing을 적용하기 위해서 @EntityListeners어노테이션을 추가합니다
  2. 공통 매핑 정보가 필요할 때 사용하는 어노테이션으로 부모 클래스를 상속 받는 자식 클래스에 매핑 정보만 제공합니다.
  3. 엔티티가 생성되어 저장될 때 시간을 자동으로 저장합니다.
  4. 엔티티의 값을 변경할 때 시간을 자동으로 저장합니다.

BaseEntity는 위에서 만든 BaseTimeEntity를 상속받고 있습니다. 등록일,수정일 등록자,수정자를 모두 갖는 엔티티는 BaseEntity를 상속받으면 됩니다.

 

BaseEntity

package com.shop.entity;

import lombok.Getter;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;

@EntityListeners(value = {AuditingEntityListener.class})
@MappedSuperclass
@Getter
public abstract class BaseEntity extends BaseTimeEntity{

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

    @LastModifiedBy
    private String modifiedBy;

}

Member 엔티티에 Auditing 기능을 적용하기 위해서 BaseEntity 클래스를 상속받도록 하겠습니다.

 

Member

public class Member extends BaseEntity{

 

회원 엔티티 저장 시 자동으로 등록자,수정자,등록시간,수정시간이 저장되는 테스트 코드를 작성하겠습니다.

 

MemverTest

package com.shop.entity;

import com.shop.repository.MemberRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.TestPropertySource;

import javax.persistence.EntityManager;
import javax.persistence.EntityNotFoundException;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;

@SpringBootTest
@Transactional
@TestPropertySource(locations = "classpath:application-test.properties")
public class MemberTest {
    @Autowired
    MemberRepository memberRepository;

    @PersistenceContext
    EntityManager em;

    @Test
    @DisplayName("Auditing 테스트")
    @WithMockUser(username = "gildong", roles = "USER")
    public void auditingTest(){
        Member newMember = new Member();
        memberRepository.save(newMember);

        em.flush();
        em.clear();

        Member member = memberRepository.findById(newMember.getId())
                        .orElseThrow(EntityNotFoundException::new);

        System.out.println("register time:" +member.getRegTime());
        System.out.println("update time : " + member.getUpdateTime());
        System.out.println("create member : " + member.getCreatedBy());
        System.out.println("modify member : " + member.getModifiedBy());
    }
}

1.스프링 시큐리티에서 제공하는 어노테이션으로 @WithMockUser에 지정한 사용자가 로그인한 상태라고 가정하고 테스트를 진행할 수 있습니다.

 

member 엔티티를 저장할 때 등록자나 등록일을 지정해주지 않았지만 저장 시간과 현재 로그인된 계정의 이름으로 지정된 것을 확인할 수 있습니다.

 

나머지 엔티티도 BaseEntity를 상속받도록 수정하겠습니다. 엔티티에 등록 시간 (regTime)과 수정 시간(update Time)이 맴버 변수로 있었다면 삭제후 상속합니다. 반복적인 내용이므로 OrderItem엔티티만 예시 코드로 작성하겠습니다. Cart,CartItem,Item,Order도 똑같이 수정합니다.

 

 

OrderItem

 

package com.shop.entity;


import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.time.LocalDateTime;

@Entity
@Getter
@Setter
public class OrderItem extends  BaseEntity{//<- 추가

    @Id
    @GeneratedValue
    @Column(name = "order_item_id")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "item_id")
    private Item item;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "order_id")
    private Order order;

    private int orderPrice;//주문가격

    private int count;// 수량

    private LocalDateTime regTime;  //삭제
    private LocalDateTime updateTime; // 삭제 

}

1. 기존에 있던 regTime, updateTime 변수를 삭제하고 BaseEntity를 상속받도록 소스코드를 수정합니다.

 

정말 중요한 내용인대 이해 되지 않는 부분이 있다면 다시 확인해야 합니다. 엔티티가 서로 복잡한 연관 관계를 맺을 때 성능 이슈가 있기도 합니다. 지연 로딩을 통해서 쿼리문이 필요할 때만 실행되도록 최적화를 하였습니다. 결국 성능 저하는 데이터를 저장하거나 수정할 때 일어나기보다는, 데이터를 조회할 때일어납니다. 다음은 조회할 때 최적화를 할 수 있는 방법이 있는지 방법을 찾아 보겠습니다.

'Spring > Spring JPA' 카테고리의 다른 글

영속성 전이  (0) 2023.03.06
연관 관계 매핑 종류  (1) 2023.03.05
@Query  (0) 2023.03.02
엔티티 매핑 관련 어노테이션  (0) 2023.02.28
JPA  (0) 2023.02.28