한줄느낀점 : 롬복을 쓰면서 record도입은 아직 시기상조인건가…
일 하다가…
몽고로 신나게 개발을 하다가, 자꾸 MongoRepository.findById() 를 수행하는데 MappingInstantiationException 가 발생한다…
로그 트레이스를 보니까 다음과 같이 뜬다..
(중요한 기업비밀-??- 일 것 같은 클래스명은 한글로 대체함)
org.springframework.data.mapping.model.MappingInstantiationException: Failed to instantiate 리턴 클래스명 using constructor 생성자 메소드 with arguments 생성자 값 Caused by: java.lang.IllegalArgumentException: Parameter 필드명 must not be null! at org.springframework.util.Assert.notNull(Assert.java:201) at 리턴 클래스명_Instantiator_dbpwc5.newInstance(Unknown Source) at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator$EntityInstantiatorAdapter.createInstance(ClassGeneratingEntityInstantiator.java:251) ... 112 more
라고 하네….
몽고의 도큐먼트 내 필드가 존재하지 않거나 null인게 있는데
자바 클래스에 이를 매핑하려 할 때 에러가 발생한 것이다.
이해하기 쉽게 대충 수도코드 형식의 코드로 보자면..
몽고
{
_id : "1234",
name : "zaksal58",
height : 100,
weight : 100
}
@Builder // 알지? 롬복 public record MyProfile ( String name, long height, long weight, long shoeSize //신발 사이즈 )}
이런 상황이다.
- 몽고의 도큐먼트엔 신발 사이즈가 없다
- 자바의 MyProfile레코드의 shoeSize 변수는 primitive data type이다.
이 경우, SELECT이후 매핑을 할 때 저 위에 있는 익셉션을 100%의 확률로 뿜뿜한다…..
익셉션을 잘 보면, 몽고로부터 가져오(려다 실패하)는 필드명이 null일 경우에 나는 익셉션이고,
자바의 primitive data type은 null값으로의 assign을 원천적으로 허용하지 않는다.

1차원적인 해결방법은 다음과 같다
- 자바에 null이 assign되지 않게끔 한다.
- 불가능하다. 가능하게 소스를 어찌어찌 꿰멘다 해도 개뻘짓임. 문제의 클래스에서 필드 하나 추가되는순간 서비스 장애남
- 자바에서 null도 받아준다
- Wrapper class가 있지 않은가?
그래서 일단 Wrapper class를 사용해서 1차 해결을 했다
근데 좀 꺼림칙하다…
해결방법이 너무 1차원적이고, 직관적으로 생각했을때 null이 assign되는 NPE의 구멍이 만들어지는걸 내 스스로 남기는 것 같아 양심의 가책도 느껴지고…
그리고 팀 내에서 primitive data type이 존재하는 자료형은 그대로 살리는 방향을 지향하고 있다. 이 부분은 나도 동의하고 있고, 가뜩이나 먼옛날 라떼엔… NPE로 허구헌날 뻑뻑뻑 서비스 터져나갔었던 추억이 있어서, (어쩔 수 없는 경우가 아닌 한)nullable한 자료형을 사용하는 걸 별로 좋아하지 않는다. 부가적으로 박싱-언박싱의 작디작은 시간낭비도 있고..
그런데, 서비스코드 다른 영역에서 findById()를 이렇게 하는 곳이 있는데, 여기선 익셉션이 발생하지 않는다. 음… 그래서 wrpper class를 사용하지 않고 해결할 수 있는 해결책에 대해 방법을 찾아보았다.
@Builder
@NoArgsConstructor // 요거 추가
@AllArgsConstructor // 요거 추가
public class MyProfileClass { // 레코드가 클래스로
String name;
long height;
long weight;
long shoeSize;
}
요렇게
- 클래스에다 NoArgs, AllArgs 어노테이션을 붙인 채로
- 레코드를 클래스로 변경한 채로
FindById() 를 쓰니까 익셉션이 일어나지 않는다.
어… 그럼 현재 문제가 있는 MyProfile 레코드에다가 NoArgs, AllArgs 어노테이션을 추가하면 문제가 해결 되겠구만

음… 롬복이 이걸 지원해주지 않네 아…. (참고로 자바17 + 롬복 6.5버전 -나름 현재기준 최신버전-임…)
왜 이런 문제가 발생할까?
문제의 해결방법에 앞서….더 중요한 ‘왜 이 문제가 발생하는지’ 에 대해서 찾아보았다.
우선…. 생성자 관련한 익셉션이 발생해서 관련된 문서를 찾아보았다.
(‘모르면 무조건 매뉴얼을 봐야 하는’건 동서고금을 막론하고 가장 정확하고 빠른 길이다. 물론 소스코드를 보면 좋겠지만…실력이 없어서…해석이 안됨..)
- 도대체 스프링 몽고는 쿼리한 결과를 Bean에 매핑을 할 때, 어떤 방법으로 생성자를 찾나
https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#mapping-chapter
여기 들어가보면… 다음과 같은 문구가 있다.

대충 해석해보면
- @PersistenceCreator 어노테이션이 붙은 팩토리메소드가 있으면 그걸 사용한다.
- 생성자 한개가 있으면, 그걸 사용한다.
- 생성자가 여러개 있는 경우라면, 그때 @PersistenceCreator어노테이션이 붙은걸 사용한다.
- NoArgsConstructor가 있으면, 다른 생성자는 무시된다
그렇군…
레코드 객체생성에서 왜 익셉션이 났냐면..
최초 문제가 발생했던 MyProfile 레코드는 Builder어노테이션에 의해, AllArgs가 자동으로 생성되어있다.
(Builder와 AllArgs의 관계에 대해서는 https://devlog-wjdrbs96.tistory.com/419 에 매우매우 잘 정리되어있음.ㅇㅇ)
그러면서 상기 객체생성 조건 2번에 부합하면서, 스프링은 자동적으로 AllArgs를 통해 객체를 생성하려 한다. 아마 이런 식으로 구현이 되겠지.
몽고 데이터..
{
_id : "1234",
name : "zaksal58",
height : 100,
weight : 100
}
@Builder // 알지? 롬복
public record MyProfile (
String name,
long height,
long weight,
long shoeSize //신발 사이즈
public MyProfile(String name, long height, long weight, long shoeSize) { // 롬복 Builder에 의해 자동 생성된 AllArgs...
this(name, height, weight, shoeSize);
}
)}
public static void main() {
var result = new MyProfile("1234","zaksal58",100,100,null);
}
이래서 shoeSize값에 null이 assign되면서 오류가 났던 것이다….
그래서 최종 해결은…
간단하다… 그냥 레코드 사용을 포기하고 클래스로 가는걸로…
- 레코드는 NoArgsConstrtuctor를 만들 수가 없는 구조
- 스프링 몽고의 객체 생성과 프로퍼티 생성 로직에 어울리지 않음..
- 스프링 몽고의 매핑 챕터에 다음과 같이 되어있음.
- https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#mapping.general-recommendations
- 롬복을 쓰란다… 근데 레코드로는 이 사용이 매우 제한적이다….