상세 컨텐츠

본문 제목

JPA Auditing 구현 (Custom EntityListener 적용)

Spring/JPA

by Chan.94 2024. 8. 28. 10:57

본문

반응형

JPA Auditing이란

  • 엔티티(Entity)에 대한 생성, 수정, 삭제 등의 작업에 대한 추적 정보를 자동으로 기록해 주는 기능이다.
  • 주로 엔티티가 언제 생성되었는지, 누가 생성했는지, 언제 수정되었는지, 누가 수정했는지를 자동으로 관리하는 데 사용된다.
  • 이 기능은 데이터의 변경 이력을 추적하거나, 데이터의 무결성을 유지하기 위해 매우 유용하다.
  • 즉, Entity의 이벤트를 감시하는 기능이다.

 

개발을 하다보면 공통적으로 도메인들이 가지고 있는 필드나 컬럼들이 존재할 것이다. 대표적으로 생성일자, 수정일자, 식별자 같은 컬럼이 있을 것이다.

도메인마다 공통으로 존재한다는 의미는 결국 코드가 중복된다는 말과 동일하다.

중복을 제거하고 비지니스에 집중하도록 하는 것이 개발자의 숙명이 아니겠는가.

 

따라서, JPA Auditing는 Entity의 변경 이력 관리에 필요한 기본적인 기능을 자동화하여 코드의 간결함과 유지보수성을 높이는 데 기여한다. 이를 통해 엔티티 생성과 수정 작업에 대해 일관성 있고 신뢰할 수 있는 이력을 관리할 수 있다.

Auditing 적용 - Standard (생성시간, 수정시간)

1) Auditing 활성화

  • 우선 @EnableJpaAuditing 어노테이션을 사용하여, Auditing을 활성화해야 한다.
  • Application 클래스에 붙이거나, @Configuration 어노테이션이 사용된 클래스에 붙이면 된다.
@EnableJpaAuditing
@SpringBootApplication
public class MainDevApplication {
    public static void main(String[] args) {
        SpringApplication.run(MainDevApplication.class, args);
    }
}

 

2) 공통 Entity작성하기

Entity마다 중복되는 소스이기에 별도의 클래스로 작성하고 다른 Entity에서 상속받아 사용하도록 한다.

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

    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime fstEnrDtm;

    @LastModifiedDate
    private LocalDateTime lstChgDtm;
}

 

@EntityListeners

  • Auditing을 적용할 Entity 클래스에 @EntityListeners 어노테이션을 적용해야 한다.
  • 해당 어노테이션은 Entity의 변화를 감지한다.
  • 이 어노테이션의 파라미터에 이벤트 리스너를 넣어줘야 한다.
    AuditingEntityListener 클래스는 JPA에서 제공하는 이벤트 리스너로 Entity의 영속, 수정 이벤트를 감지하는 역할을 한다.

@MappedSuperclass

  • 공통 매핑 정보가 필요할 때 부모 클래스에 선언된 필드를 상속받는 클래스에서 그대로 사용할 때 사용한다.

@CreateDate

  • Entity가 생성됨을 EntityListener가 감지하고 그 시점을 필드에 기록한다.
  • 수정되면 안 되는 컬럼에는 @Column(updatable = false) 옵션을 적용한다.

@LastModifiedDate

  • Entity가 수정됨을 EntityListener가 감지하고 그 시점을 필드에 기록한다.

 

이렇게 적용하고 테스트해 보면 기본적인 JPA Auditing 기능은 구현되었다.

생성시간, 수정시간 외에 생성자, 수정자에 대한 정보를 설정하려면 AuditorAware 구현체를 만들어야 한다.


Auditing 적용 - 생성자, 수정자

1) AuditorAware 구현체 만들기

@CreatedDate, @LastModifiedDate는 구현체 없이 사용할 수 있지만 @CreatedBy, @LastModifiedBy는 구현체가 필요하다.

public class AuditorAwareImpl implements AuditorAware<String> {

    @Override
    public Optional<String> getCurrentAuditor() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication.getPrincipal().equals("anonymousUser")) {
            return Optional.of("anoymousUser");
        }

        PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal();
        return Optional.of(String.valueOf(principalDetails.getUserId()));
    }
}

 

JWT 인증을 구현해 놓은 환경에서 진행 중이기 때문에 변경하는 USER의 정보를 Security Context에서 가지고 올 수 있다.

이 부분은 각자 상황에 맞게 구현하면 된다.

 

2) Configuration 파일 생성

@Configuration
@EnableJpaAuditing
public class JpaConfiguration {
    @Bean
    public AuditorAware<String> auditorProvider() {
        return new AuditorAwareImpl();
    }
}

 

3) 공통 Entity작성

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

    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime fstEnrDtm;
    
    @CreatedBy
    @Column(updatable = false)
    private String fstEnrUser;

    @LastModifiedDate
    private LocalDateTime lstChgDtm;
    
    @LastModifiedBy
    private String lstChgUser;
}

 

@CreatedBy

  • Entity가 생성됨을 EntityListener가 감지하고 그 시점을 필드에 기록한다.
  • 이 필드는 AuditorAware를 구현하여 사용자 정보를 제공해야 합니다.

 

@LastModifiedBy

  • Entity가 수정됨을 EntityListener가 감지하고 그 시점을 필드에 기록한다.
  • 이 필드는 AuditorAware를 구현하여 사용자 정보를 제공해야 합니다.

위 설정까지 진행한 후 JPA를 사용해 INSERT, UPDATE를 해보면 @CreatedBy와 @LastModifiedBy설정이 추가된 필드에 자동으로 값이 설정되는 것을 확인할 수 있다.


이제는 기본 AuditingEntityListener대신 Custom EntityListener를 구현하여 사용해 보자.


Auditing 적용 - Custom EntityListener

CustomEntityListener를 구현하고 생성시간, 수정시간, 생성자, 수정자 이외에 IP, URI경로 등 정보도 자동으로 설정해 보자.


1) CustomEntityListener 생성

@Component
@RequiredArgsConstructor
public class CustomEntityListener{
    
    private final RequestHeader requestHeader;

    @PrePersist
    public void prePersists(Object entity) {
        if(entity instanceof BaseEntity) {
            ((BaseEntity) entity).setFstEnrUser(requestHeader.getUserId());
            ((BaseEntity) entity).setLstChgUser(requestHeader.getUserId());
            
            ((BaseEntity) entity).setFstEnrDtm(LocalDateTime.now());
            ((BaseEntity) entity).setLstChgDtm(LocalDateTime.now());
            
            ((BaseEntity) entity).setFstEnrUrl(requestHeader.getUrlPath());
            ((BaseEntity) entity).setLstChgUrl(requestHeader.getUrlPath());
        }
    }

    @PreUpdate
    public void preUpdate(Object entity) {
        if(entity instanceof BaseEntity) {
            ((BaseEntity) entity).setLstChgUser(requestHeader.getUserId());
            
            ((BaseEntity) entity).setLstChgDtm(LocalDateTime.now());
            
            ((BaseEntity) entity).setLstChgUrlPath(requestHeader.getUrlPath());
        }
    }
    
    ...
}
  • @PrePersist : Entity가 데이터베이스에 저장되기 전 실행된다.
  • @PostPersist : Entity가 데이터베이스에 저장된 후 실행된다.
  • @PreUpdate : Entity가 데이터베이스에 수정되기 전 실행된다.
  • @PostUpdate : Entity가 데이터베이스에 수정된 후 실행된다.
  • @PreRemove : Entity가 데이터베이스에 삭제되기 전 실행된다.
  • @PostRemove : Entity가 데이터베이스에 삭제된 후 실행된다.

Custom EntityListener를 생성하여 정보를 설정하려면 Request 한 User의 정보나 URI 경로 등을 알아야 한다.

코드에 나와있는 RequestHeader는 JWT인증 시 User의 정보와 URI경로 등의 정보를 가지고 있다.

 

이 부분은 각자 상황에 맞게 구현하자.

테스트를 위함이라면 하드코딩하고 테스트를 진행해도 된다.

 

2) Entity에 CustomEntityListener 적용

@Setter
@Getter
@MappedSuperclass
//@EntityListeners(AuditingEntityListener.class)
@EntityListeners(CustomEntityListener.class)
public class BaseEntity {

    @JsonFormat(pattern = "yyyyMMddHHmmss")
    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime fstEnrDtm;
    
    //@CreatedBy AuditingEntityListener에서 사용
    @Column(updatable = false)
    private String fstEnrUser;

    @Column(updatable = false)
    private String fstEnrUrl;

    @JsonFormat(pattern = "yyyyMMddHHmmss")
    @LastModifiedDate
    private LocalDateTime lstChgDtm;
    
    //@LastModifiedBy AuditingEntityListener에서 사용
    private String lstChgUser;

    private String lstChgUrl;
}

 

 


EntityListener를 통해 엔티티의 생명주기 이벤트를 감지하고, 원하는 로직을 자동으로 실행할 수 있다.

이를 활용하면 데이터 변경 시 감사 로깅, 데이터 무결성 유지, 특정 조건에 따른 추가 작업 등을 쉽게 구현할 수 있다.

반응형

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

JPA 영속성 컨텍스트 이해 (Persistence Context)  (14) 2024.08.26
[Spring] JPA Example (JpaRepository)  (12) 2023.03.30
ORM, JPA에 대한 이해  (12) 2023.03.29

관련글 더보기

댓글 영역

>