스프링부트

spring data MongoDB - auditing 설정 및 활용

알쓸개잡 2024. 2. 26.

Spring Data는 엔티티(도큐먼트)가 생성되거나 수정될 때 생성자, 수정자, 생성시간, 수정시간을 투명하게 추적할 수 있도록 Auditing 기능을 제공한다. 이번 포스팅에서는 spring data MongoDB 에서 Auditing 기능을 사용할 수 있는 방법에 대해서 정리하고자 한다.

 

Auditing 기능 활성화

@Configuration
@EnableMongoAuditing
public class MongoDBConfigurationByDirect {
...
}

@EnableMongoAuditing 어노테이션은 Spring Data MongoDB에서 제공하는 auditing 기능을 활성화하기 위해서 사용한다.

@EnableMongoAuditing을 사용하면 엔티티가 데이터베이스에 저장되거나 업데이트될 때 자동으로 생성시간, 수정시간, 생성자, 수정자 등의 정보를 기록할 수 있다.

 

@EnableMongoAuditing 어노테이션은 다음과 같은 속성을 제공한다.

  • auditorAwareRef : AuditorAware 인터페이스를 구현한 빈의 이름을 지정한다. 이를 통해 엔티티의 생성자와 수정자를 결정하는 로직을 커스터마이징 할 수 있다.
  • setDates : 생성 및 수정 날짜를 자동으로 설정할지 여부를 결정한다. (default: true)
  • modifyOnCreate : 엔티티 생성 시 'lastModifiedDate'를 설정할지 여부를 결정한다. (default: true)
  • dateTimeProviderRef : 날짜와 시간을 제공하는 빈의 이름을 지정한다. 이를 통해 날짜와 시간 값의 생성 방식을 커스터마이징 할 수 있다.

감사 데이터 추가

엔티티(도큐먼트) 클래스에 감사데이터로 사용할 필드에 @CreatedBy (생성자), @CreatedDate (생성시간), @LastModifiedBy (수정자),  @LastModifiedDate (수정시간) 어노테이션을 지정한다.

@Document
@Getter
@Setter
public abstract class Auditable implements Persistable<UUID> {

    @CreatedBy
    private String createdBy;

    @CreatedDate
    private Instant createdDate;

    @LastModifiedBy
    private String lastModifiedBy;

    @LastModifiedDate
    private Instant lastModifiedDate;

    @Override
    public boolean isNew() {
        return createdDate == null;
    }
}

일반적으로 Audit 필드만 정의된 base 클래스를 정의 후 이를 상속하여 사용하는 패턴을 많이 사용한다.

Persistable 인터페이스를 확장하여 isNew() 메서드를 오버라이드 하여 저장되는 document 가 새로 생성된 것인지를 판별할 수 있도록 한다. 샘플 코드에서는 createdDate 필드가 존재하지 않는 경우 새롭게 생성되는 문서라고 판단한다.

isNew() 메서드를 정의하지 않으면 Document 저장시 createdDate 필드는 auditing 되지 않고 lastModifiedDate 필드만 업데이트된다. 해당 문서가 새로 생성되는 것인지 여부를 판단할 수 없어서 인듯 하다.

 

@Document(collection = "person")
@Getter
@ToString
public class PersonDocument extends Auditable {
    @Id
    private final UUID personId;
    //spring.data.mongodb.auto-index-creation 설정이 true인 경우 @Indexed로 지정된 컬럼에 index가 생성된다.
    private final String personName;
    private final Integer personAge;
    @Setter private Gender personGender;
    @Setter private String personNation;
    private final String comment;


    @Builder
    public PersonDocument( UUID personId, String personName, Integer personAge,
                           Gender personGender, String personNation, String comment ) {
        this.personId = personId;
        this.personName = personName;
        this.personAge = personAge;
        this.personGender = personGender;
        this.personNation = personNation;
        this.comment = comment;
    }

    @Override
    public UUID getId() {
        return personId;
    }
}

Auditing을 적용하고자 하는 Document는 Auditable을 확장하여 사용한다.

Auditable 클래스는 Persistable 인터페이스를 확장하므로 getId() 메서드를 재정의 해줘야 한다.

 

AuditorAware 구현

AuditorAware 인터페이스를 구현하여 이를 빈드로 등록하면 @CreatedBy, @LastModifiedBy 어노테이션에 지정된 생성자와 수정자 정보를 결정하는 로직을 커스텀하게 작성할 수 있다.

일반적으로 Spring Security와 함께 현재 인증된 사용자의 정보를 SecurityContextHolder 스레드 로컬로부터 가져와서 해당 사용자의 정보를 생성자, 수정자로 자동 지정할 수 있다.

 

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

        if (authentication == null || !authentication.isAuthenticated()) {
            return Optional.of("system");
        }

        return Optional.ofNullable(authentication.getName());
    }
}

위 코드는 AuditorAware<String> 인터페이스를 구현하며, <String>은 생성자, 수정자의 데이터 타입을 나타낸다.

AuditorAwareImpl 클래스는 빈으로 등록되어야 한다.

AuditorAware 인터페이스는 getCurrentAuditor() 메서드 하나만 가진 함수형 인터페이스다.

따라서 빈을 생성 시에 lambda로 생성이 가능하다.

@EnableMongoAuditing 어노테이션의 auditorAwareRef 속성에 빈 이름을 지정한다.

import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.config.EnableMongoAuditing;

@Configuration
@EnableMongoAuditing(auditorAwareRef = "auditorAwareImpl")
public class MongoConfig {
    // AuditorAware 빈 정의
    @Bean
    public AuditorAware<String> auditorAwareImpl() {
        return new AuditorAwareImpl();
    }
}
AuditorAware 클래스를 구현한 빈이 반드시 auditorAwareRef 속성에 지정되지 않아도 동작하지만 AuditorAware를 구현한 클래스는 반드시 빈으로 등록되어야 한다.

AuditorAware 인터페이스의 getCurrentAuditor() 메서드가 반환하는 값은 엔티티가 최초로 저장될 때 @CreatedBy 어노테이션이 지정된 필드와 @LastModifiedBy 어노테이션이 지정된 필드에 모두 할당된다. 

이후 엔티티가 수정될 대마다 @LastModifiedBy 어노테이션이 지정된 필드는 새로운 수정자 정보로 업데이트하지만 @CreatedBy 필드는 최초 생성 시 할당된 값이 유지된다.

 

생성된 Document

Auditing 이 적용되어 생성된 document 샘플은 다음과 같다.

{
  "_id": {
    "$binary": {
      "base64": "P7jmfXy7RtecgiWcweHsBQ==",
      "subType": "04"
    }
  },
  "personName": "test-name",
  "personAge": 100,
  "personGender": "성별 없음",
  "personNation": "Korea",
  "comment": "dGVzdC1uYW1lIOy9lOupmO2KuA==",
  "createdBy": "system",
  "createdDate": {
    "$date": "2024-02-25T16:39:14.264Z"
  },
  "lastModifiedBy": "system",
  "lastModifiedDate": {
    "$date": "2024-02-25T16:39:14.264Z"
  },
  "_class": "com.example.springmongodb.documents.PersonDocument"
}

 

댓글

💲 추천 글