MongoDB 애플리케이션을 운영하다 보면 데이터 생성, 수정 이력(작성자, 생성시간, 수정자, 수정시간) 필드가 대부분 공통으로 존재한다. Spring Data MongoDB의 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 : 날짜와 시간을 제공하는 빈의 이름을 지정한다. 이를 통해 날짜와 시간 값의 생성 방식을 커스터마이징 할 수 있다.
Auditable 베이스 클래스 만들기
여러 문서에서 공통으로 사용할 기본 감시 필드를 생성한다.
엔티티(도큐먼트) 클래스에 감사데이터로 사용할 필드에 @CreatedBy (생성자), @CreatedDate (생성시간), @LastModifiedBy (수정자), @LastModifiedDate (수정시간) 어노테이션을 지정한다.
@Getter
@Setter
public abstract class Auditable implements Persistable<UUID> {
@Id
private UUID id;
@CreatedBy
private String createdBy;
@CreatedDate
private Instant createdDate;
@LastModifiedBy
private String lastModifiedBy;
@LastModifiedDate
private Instant lastModifiedDate;
@Transient
private boolean isNew;
@Override
public boolean isNew() {
return isNew || id == null;
}
}
일반적으로 Audit 필드만 정의된 base 클래스를 정의 후 이를 상속하여 사용하는 패턴을 많이 사용한다.
Persistable 인터페이스를 확장하여 isNew() 메서드를 오버라이드 할 수 있는데, 이 메서드는 저장되는 document 가 새로 생성된 것인지를 판별할 수 있도록 한다. 샘플 코드에서는 isNew 필드 값이 true 혹은 id가 null인 경우 새로 생성하는 document로 판단한다.
isNew 필드를 추가한 이유는 새로 생성한 document라고 하더라도 사용자가 직접 id를 지정할 수 있기 때문이다.
isNew() 메서드를 정의하지 않으면 Document 저장시 createdDate 필드는 auditing 되지 않고 lastModifiedDate 필드만 업데이트된다. 해당 문서가 새로 생성되는 것인지 여부를 판단할 수 없어서 인듯 하다.
@Document(collection = "person")
@Getter
@Setter
@ToString
public class PersonDocument extends Auditable {
private final String personName;
private final Integer personAge;
private Gender personGender;
private String personNation;
private final String comment;
public static PersonDocument create( String personName, Integer personAge,
Gender personGender, String personNation, String comment ) {
PersonDocument doc = new PersonDocument();
doc.setPersonName(personName);
doc.setPersonAge(personAge);
doc.setPersonGender(personGender);
doc.setComment(comment);
doc.setNew(true);
return doc;
}
}
Auditing을 적용하고자 하는 Document는 Auditable을 확장하여 사용한다.
AuditorAware 구현
실제로 누가 생성하고 수정했는가를 기록하기 위해서 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 필드는 최초 생성 시 할당된 값이 유지된다.
createdDate, lastModifiedDate 필드 타입은 Instant 타입을 사용
타임존 문제를 피하기 위해서는 ISO-8601 + Instant 조합을 사용하는 것이 좋다.
spring data mongodb는 document를 저장시 내부적으로 ISO-8601(UTC) 형식의 날짜로 변환하여 저장한다.
이를 통해서 타임존 문제를 해결 할 수 있다. 만약 LocalDateTime 필드를 사용하게 된다면 타임존 정보가 없기 때문에 시간 처리에 대한 다양한 문제가 발생할 수 있다.
Instant 타입을 사용함으로써 얻는 이점은 다음과 같다.
- 서버/클라이언트/DB 타임존에 대한 혼란을 해소한다.
- 글로벌 서비스를 하는데는 필수적이다.
- MongoDB의 날짜 타입(Date)와 매핑이 가능하다.
생성된 Document
Auditing 이 적용되어 생성된 document 샘플은 다음과 같다.
{
"_id": {
"$binary": {
"base64": "P7jmfXy7RtecgiWcweHsBQ==",
"subType": "04"
}
},
"personName": "test-name",
"personAge": 100,
"personGender": "성별 없음",
"personNation": "Korea",
"comment": "test-name 코멘트",
"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"
}
끝.
'스프링부트' 카테고리의 다른 글
| Spring Data Redis - Auto Configuration을 이용한 Redis 연결 설정 (0) | 2024.03.24 |
|---|---|
| Spring Data MongoDB - Custom Repository 사용하기 (0) | 2024.03.13 |
| spring data MongoDB - Repository 사용하기 (0) | 2024.02.20 |
| spring data MongoDB - 필드 변환하기 (Converter) (0) | 2024.02.04 |
| spring data MongoDB - ScopedValue를 이용한 MongoDB multiple database 연결하기 (0) | 2024.01.06 |
댓글