본문 바로가기
Java Spring

MapStruct 사용 시 여러 Constructor가 있을 때의 문제점

by GGShin 2022. 7. 31.

객체 간 mapping을 위한 코드의 양을 많이 줄여주는 Mapstruct.

구체적인 사용법을 모르고 있다가 의도대로 MapperImpl이 생성되지 않게되며 파워 구글링 덕에 궁금증을 해결했습니다.

공식 문서가 왜인지 한번에 나오지 않아서 이 사이트 저 사이트 전전하다가 겨우 찾았습니다 ㅎㅎ 다행히도 공식 문서 안에는 궁금해 하던 내용이 잘 나와있었고, 이후에는 최소한 이 부분에 있어서는 헤매지 않을 것 같아서 안심입니다.

 

먼저 궁금증이 폭발했던 상황은 이렇습니다.

Spring Data JPA를 사용하고 있기 때문에 Entity를 만들었고, PostDtoToEntity mapper가 필요했습니다. 

 

처음 Entity code는 아래와 같았습니다. @Entity를 사용했기 때문에 @NoArgsConstructor가 필수로 들어가야해 붙여주었습니다. 그리고 사실상 제가 원하는 PostDtoToEntity 시에는 아래에 parameter를 4개 갖는 constructor 형태를 이용해 mapping 되기를 원했습니다. 빌드를 해보니 mapping은 제가 원하는 constructor가 아니라 No parameter consturctor로 이루어졌습니다.

 

그때까지만 해도 Mapstruct가 Constructor의 parameter 수를 보고 먼저 채택하는 constructor가 있다는 생각은 하지 못하고 setter가 없어서 parameter가 없는 constructor만 사용이 가능한가 보다라고 생각해서 setter를 만들었습니다. setter를 만들었더니 아래 나온 것처럼 되기는 되는데, 내가 정의한 constructor를 사용한 것이 아니라 의도되로 된 것 같지 않아 이상했습니다. 아니나 다를까 만들었던 constructor에서 password parameter를 제거하고 몇 번이고 build를 새로 해봐도 setPassword까지 mapping되는 것이 확인되었습니다. 

 

그리고 다른 하나는 PostoDtoToEntity method 아래에 EntityToResultDto method가 있었는데, 그 method는 setter가 아니라 constructor로 mapping이 되고 있었습니다. 그렇기 때문에 더욱 그 둘의 차이점이 무엇인 걸까를 한참 고민하게 되었습니다. 

 

결국 공식문서에서 해답을 찾았는데 답은 Mapstruct가 mapping을 할 도구를 찾을 때는 우선순위가 있기 때문이어서 였습니다.

제일 우선순위는 builder이고 (builder가 없으면 setter인 것 같았습니다.)

 

-> 그 다음은 하나만 정의된 constructor

 

-> constructor가 두개 이상인 경우:

  • @Default가 붙은 것이 먼저
  • 혹은 public인 constructor(여러개 중 하나만 public이어야 합니다.)
  • 혹은 NoArgsConstructor
  • 우선순위가 중복되는 constructor가 다수인 경우는 compile error 발생

이런 순서대로 mapping 도구가 선택되는 것이었습니다. 

제가 만든 Member Entity는 @Entity가 붙어있었기에 필수적으로 @NoArgsConstructor가 있어야 했고, builder가 없었기 때문에 NoArgsConstructor로 mapping이 되었던 것입니다. 

반면에 ResponseDto의 경우 @Entity가 없었고 builder나 setter도 없었고 @AllArgsConstructor 하나만 있었기 때문에 자연스럽게 모든 parameter가 있는 constructor가 사용되었던 것입니다. 

 

그래서 '아! 그러면 @Default를 내가 원하는 constructor에 붙이면 되겠구나' 싶어서 붙여보았더니 호락호락하지 않았습니다 ㅎㅎ

@Default는 이렇게 두개가 나오는데, 둘 다 Constructor에는 사용이 불가능한 anno라고 되어있었습니다. 

 

혹시 이 글을 보시는 분 중에 어떻게 @Default를 사용하는 것인지 아신다면 댓글로 알려주시면 감사하겠습니다. 다른 라이브러리를 추가해야하는 건지,, 좀 더 알아 보아야겠습니다. 

 

대신에 찾은 방법은 @Builder를 원하는 constructor에 붙여두는 것이었습니다.

Entity

일부러 아까 전 setter 사용시와 같은 문제(?)를 없애기 위해 password parameter는 제거하고 테스트해보았습니다.

그랬더니 아래처럼 원하는 parameter만을 선택해서 mapping이 되었습니다. 

MapperImpl

 

검색을 많이 해봐도 정보는 많이 없고, 이전에 사용한 예시를 보면서 이것 저것 시도도 고민도 많이 해보면서 힘들기는 했지만 어찌저찌 문서를 찾아서 한번에 궁금증이 해결이 되었으니 만족합니다 ㅎㅎ 앞으로 도구를 사용할 때는 물건을 샀을 때처럼 꼭 instructions를 잘 읽고 사용해야겠다고 다시 한 번 느낀 순간이었습니다. 앞으로 나오게 될 도구들도 친절한 manual을 만들어주기도 바라봅니다 ㅎㅎ

 

감사합니다.

반응형