Spring Boot에서 JPA Auditing 기능 활용하기

Auditing 활성화 설정

JPA Auditing 기능을 사용하려면 먼저 설정 클래스에 @EnableJpaAuditing 어노테이션을 추가해야 합니다.

package com.example.config;

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

@Configuration
@EnableJpaAuditing
public class JpaAuditingConfiguration {
}

엔티티에 Auditing 필드 추가

엔티티에는 생성 시간, 수정 시간, 생성자, 수정자 정보를 저장할 필드를 정의합니다.

@CreatedDate
@Basic
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;

@LastModifiedDate
@Basic
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@CreatedBy
@Basic
@Column(name = "creator")
private String creator;

@LastModifiedBy
@Basic
@Column(name = "modifier")
private String modifier;

또한 엔티티 클래스에 @EntityListeners 어노테이션을 적용하여 Auditing 리스너를 등록합니다.

@EntityListeners(AuditingEntityListener.class)

AuditorAware 인터페이스 구현

현재 사용자를 식별하기 위해 AuditorAware 인터페이스를 구현합니다.

public class UserAuditorAware implements AuditorAware<String> {
    @Override
    public Optional<String> getCurrentAuditor() {
        // 실제 애플리케이션에서는 SecurityContext 등을 통해 현재 사용자 정보를 가져옴
        return Optional.of("SYSTEM");
    }
}
@Component
public class SecurityAuditorAware implements AuditorAware<Long> {
    @Override
    public Optional<Long> getCurrentAuditor() {
        // Spring Security를 사용하는 경우 현재 인증된 사용자 ID 반환
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null || !authentication.isAuthenticated()) {
            return Optional.empty();
        }
        // 사용자 ID 추출 로직 구현
        return Optional.of(1L);
    }
}

AuditorAware 인터페이스는 다음과 같은 형태로 정의되어 있습니다:

public interface AuditorAware<T> {
    Optional<T> getCurrentAuditor();
}

설정 클래스에서 AuditorAware 빈을 등록합니다:

@Configuration
@EnableJpaAuditing
public class AuditingConfig {
    
    @Bean
    @ConditionalOnMissingBean
    public AuditorAware<String> auditorAware() {
        return new UserAuditorAware();
    }
}

Auditable 인터페이스 직접 구현 방식

엔티티가 Auditable 인터페이스를 직접 구현하는 방법도 가능합니다:

@Entity
@Data
@EntityListeners(AuditingEntityListener.class)
public class Member implements Auditable<String, Long, LocalDateTime> {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String username;
    
    @CreatedBy
    private String createdBy;
    
    @CreatedDate
    private LocalDateTime createdDate;
    
    @LastModifiedBy
    private String lastModifiedBy;
    
    @LastModifiedDate
    private LocalDateTime lastModifiedDate;
    
    @Override
    public Optional<String> getCreatedBy() {
        return Optional.ofNullable(this.createdBy);
    }
    
    @Override
    public void setCreatedBy(String createdBy) {
        this.createdBy = createdBy;
    }
    
    @Override
    public Optional<LocalDateTime> getCreatedDate() {
        return Optional.ofNullable(this.createdDate);
    }
    
    @Override
    public void setCreatedDate(LocalDateTime createdDate) {
        this.createdDate = createdDate;
    }
    
    @Override
    public Optional<String> getLastModifiedBy() {
        return Optional.ofNullable(this.lastModifiedBy);
    }
    
    @Override
    public void setLastModifiedBy(String lastModifiedBy) {
        this.lastModifiedBy = lastModifiedBy;
    }
    
    @Override
    public Optional<LocalDateTime> getLastModifiedDate() {
        return Optional.ofNullable(this.lastModifiedDate);
    }
    
    @Override
    public void setLastModifiedDate(LocalDateTime lastModifiedDate) {
        this.lastModifiedDate = lastModifiedDate;
    }
    
    @Override
    public boolean isNew() {
        return this.id == null;
    }
}

@MappedSuperclass를 이용한 공통 베이스 클래스

여러 엔티티에서 공통으로 사용하는 Auditing 필드를 @MappedSuperclass를 사용해 관리할 수 있습니다.

@Data
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class AuditBaseEntity {
    
    @CreatedBy
    protected String createdBy;
    
    @CreatedDate
    protected LocalDateTime createdDate;
    
    @LastModifiedBy
    protected String lastModifiedBy;
    
    @LastModifiedDate
    protected LocalDateTime lastModifiedDate;
}

실제 엔티티에서는 이 베이스 클래스를 상속받아 사용합니다:

@Entity
@Data
public class Product extends AuditBaseEntity {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long productId;
    
    private String productName;
    private BigDecimal price;
}

Auditing 동작 원리

@EnableJpaAuditing 어노테이션은 내부적으로 @Import(JpaAuditingRegistrar.class)를 포함하고 있어 Auditing 관련 설정을 처리합니다. Spring 컨테이너는 AuditingEntityListenerAuditingHandler를 주입하여 엔티티의 생성 및 수정 시점에 Auditing 정보를 자동으로 설정합니다.

AuditingEntityListener는 JPA의 콜백 메서드(@PrePersist, @PreUpdate)를 활용하여 엔티티 저장 전에 Auditing 정보를 설정합니다:

@Configurable
public class AuditingEntityListener {
    
    private @Nullable ObjectFactory<AuditingHandler> handler;
    
    public void setAuditingHandler(ObjectFactory<AuditingHandler> auditingHandler) {
        Assert.notNull(auditingHandler, "AuditingHandler must not be null!");
        this.handler = auditingHandler;
    }
    
    @PrePersist
    public void touchForCreate(Object target) {
        Assert.notNull(target, "Entity must not be null!");
        if (handler != null) {
            AuditingHandler object = handler.getObject();
            if (object != null) {
                object.markCreated(target);
            }
        }
    }
    
    @PreUpdate
    public void touchForUpdate(Object target) {
        Assert.notNull(target, "Entity must not be null!");
        if (handler != null) {
            AuditingHandler object = handler.getObject();
            if (object != null) {
                object.markModified(target);
            }
        }
    }
}

사용자 정의 엔티티 리스너

기본 Auditing 외에도 사용자 정의 리스너를 만들어 특정 시점에 추가 작업을 수행할 수 있습니다:

public class CustomAuditListener {
    
    @PrePersist
    private void beforeInsert(BaseEntity entity) {
        SysUser currentUser = getCurrentUser();
        entity.setCreatorId(currentUser.getId());
        entity.setCreatorName(currentUser.getName());
        entity.setUpdaterId(currentUser.getId());
        entity.setUpdaterName(currentUser.getName());
    }
    
    @PreUpdate
    private void beforeUpdate(BaseEntity entity) {
        SysUser currentUser = getCurrentUser();
        entity.setUpdaterId(currentUser.getId());
        entity.setUpdaterName(currentUser.getName());
    }
    
    private SysUser getCurrentUser() {
        // 현재 사용자 정보 조회 로직
        return new SysUser(1L, "admin");
    }
}

사용자 정의 리스너를 적용하려면 엔티티에 다음과 같이 설정합니다:

@Entity
@EntityListeners({AuditingEntityListener.class, CustomAuditListener.class})

태그: spring-boot jpa auditing spring-data-jpa entity-listener

5월 21일 05:35에 게시됨