Spring - Spring Data Reactive mongo MongoCustomConversions 예제

2020. 2. 3. 21:54Web/Spring

 

기본적으로 MongoDB는 ObjectId라는 유니크한 primary id를 갖는다. 하지만 @Id 어노테이션을 특정 Class로 매핑시키기 위한 방법은 없을까? 예를 들어 아래와 같은 상황이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Document
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DocumentData {
 
    @Id
    private CustomId id;
    private String value;
 
    @Data
    @AllArgsConstructor(staticName = "of")
    @NoArgsConstructor
    public static class CustomId implements Serializable {
        private String idPrefix;
        private String idDelemeter;
    }
}
cs

 

DocumentData라는 Collection이 있고, 해당 Collection에는 ObjectId 타입이 아닌 CustomId 오브젝트 타입의 Id를 넣고 싶은 것이고, 실제로 Id는 내부적으로 String 타입이며 idPrefix + idDelemeter 라는 스트링으로 매핑하고 싶다. 즉, CustomId("prefix", "!"); 로 생성된 오브젝트가 있고 이것이 실제로 Mongodb에 들어가면 _id : "prefix!" 인 형태로 저장이 하고 싶은 것이다. 이럴때는 어떻게 해야할까?

 

1
2
3
4
5
6
7
8
9
10
11
    @Bean
    public MongoCustomConversions customConversions() {
        return new MongoCustomConversions(Collections.singletonList(new CustomIdConverter()));
    }
 
    public static class CustomIdConverter implements Converter<DocumentData.CustomId, String> {
        @Override
        public String convert(DocumentData.CustomId source) {
            return source.getIdPrefix() + source.getIdDelemeter();
        }
    }
cs

 

위와 같이 MongoCustomConversions를 Bean으로 등록하면 특정 오브젝트 타입이 @Id로 Mapping 되어 있을 때, 구현한 Converter 내용에 따라 적절히 _id 값이 들어가게 된다. 위의 구현은 CustomId 오브젝트 타입이 @Id로 매핑되어 있을 때, idPrefix와 IdDelemeter를 조합하여 String Type의 _id 값을 만들어 낸다. 그리고 기존의 ObjectId로 매핑한 @Id도 그대로 사용가능하다.

 

1
2
3
4
5
6
7
8
public interface MongoRepositoryImpl extends ReactiveMongoRepository<DocumentData, DocumentData.CustomId> {}
 
@PostMapping("/mongo")
public Mono<DocumentData> save(){
    DocumentData data = new DocumentData(DocumentData.CustomId.of("prefix","!"), "value");
    mongoRepository.save(data);
    return mongoRepository.save(data);
}
cs

 

실제로 간단히 API를 만들어 요청을 보내보면 아래와 같이 데이터가 삽입되어 있다.

 

1
2
3
_id : "prefix!"
value : "value"
_class : "com.webflux.mongoexam.DocumentData"
cs

 

실제 필자도 내부적으로 자체적인 복잡한 ID 체계를 가져가기 위해 MongoCustomConversions를 빈으로 등록하여 사용하고 있다.

 

추가적으로 하나더 이슈가 있다. 만약에 특정 커스텀 클래스를 @Id class로 사용하려면 해당 클래스가 반드시 Serializable을 구현하는 클래스이어야한다! 이 이슈를 발견하게 된 것은 ReactiveMongoRepository의 findById를 사용할때 였다. 아이디클래스가 Serializable을 구현하지 않으면 예외를 내뱉는다.

 

 

{
    logEvent: "java.lang.ClassCastException",
    errorMsg: "class xxx cannot be cast to class java.io.Serializable (xxx is in unnamed module of loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader @2c4eab42; java.io.Serializable is in module java.base of loader 'bootstrap')"
}