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 컨테이너는 AuditingEntityListener에 AuditingHandler를 주입하여 엔티티의 생성 및 수정 시점에 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})