일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- nextcamp
- 삼성전자 3급
- BOJ
- 채용연계형
- Baekjoon
- 카카오
- Summer Intern
- 프로그래머스
- 2020 kakao
- 2020
- 카카오 전환
- 구현
- 백준
- 2020 카카오 인턴
- 여름 인턴십
- 14890번
- 카카오 인턴
- Lombok-MapStruct-binding
- 백트래킹
- 카카오 면접
- Dev-matching
- 카카오 서류
- 삼성A형
- 알고리즘
- 개발자
- 카카오 여름 인턴
- MapStruct
- 12100번
- 1등당첨기원
- C++
- Today
- Total
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- nextcamp
- 삼성전자 3급
- BOJ
- 채용연계형
- Baekjoon
- 카카오
- Summer Intern
- 프로그래머스
- 2020 kakao
- 2020
- 카카오 전환
- 구현
- 백준
- 2020 카카오 인턴
- 여름 인턴십
- 14890번
- 카카오 인턴
- Lombok-MapStruct-binding
- 백트래킹
- 카카오 면접
- Dev-matching
- 카카오 서류
- 삼성A형
- 알고리즘
- 개발자
- 카카오 여름 인턴
- MapStruct
- 12100번
- 1등당첨기원
- C++
- Today
- Total
슬기로운개발생활
[Spring] MapStruct 와 Lombok 본문
MapStruct: 클래스간 변환을 쉽게 해주고 변환 코드를 자동으로 생성해주는 라이브러리
Lombok: 보일러 플레이트 코드 (getter / setter / constructor / builder 등) 를 줄여주는 자동 코드 생성 라이브러리
이 두개를 섞어서 사용할 때 주의해야 할 점이 있더라...버전의 문제였다. 학부 프로젝트하면서 개발할 땐 버전에 대해 신경쓰지않았는데, 버전은 정말 중요한 요소 중 하나이고, 현재 프로젝트 기술 스택에 포함되어 있다면 릴리즈 변경 사항을 챙겨보길 바란다.
버전을 올리거나 내리는건 생각하는 것 만큼 단순한 일이 아니다....
참고 깃헙 이슈: https://github.com/mapstruct/mapstruct/issues/510
예시 클래스와 매퍼는 아래와 같고, 사용된 라이브러리 버전은 Lombok(1.18.12), MapStruct(1.3.1.Final) 이다. 또한, 해당 클래스들은 모두 같은 모듈내에 있다.
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Person {
private String name;
@JsonProperty("name")
public void setCustomName(String name) {
// 예시 코드라 아무것도 안하지만, 보통 CustomSetter에는 다른 로직이 들어갈 것이다.
this.name = "custom = " + name;
}
@JsonProperty("name")
public String getCustomName() {
return this.name;
}
public static class PersonBuilder {
public PersonBuilder customName(String name) {
return this.customName(name);
}
}
}
@Data
public class PersonRequest {
private String personName;
}
@Mapper
public interface PersonMapper {
@Mapping(target = "customName", source = "personName")
@Mapping(target = "name", ignore = true)
Person toEntity(PersonRequest req);
}
[Issue] Gradle dependencies 순서에 따라 생성되는 코드가 다르다!! AP(AnnotationProcessor) 동작 순서가 달라서
1. Lombok 다음 MapStruct: MapperImpl 은 setter 로 생성이 된다.
AP가 컴파일 첫번째 과정에서 Lombok 이 먼저 만들어놓은 getter, setter 를 그 다음에 실행된 MapStruct가 사용해서 코드를 생성할 수 있기 때문에 setter 코드를 바로 생성한다.
두번째 과정에서 Lombok이 builder를 생성하지만 MapStruct는 변하지 않는다.
implementation "org.projectlombok:lombok:1.18.12"
implementation "org.mapstruct:mapstruct:1.3.1.Final"
annotationProcessor "org.projectlombok:lombok:1.18.12"
annotationProcessor "org.mapstruct:mapstruct-processor:1.3.1.Final"
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2021-02-10T14:23:34+0900",
comments = "version: 1.3.1.Final, compiler: javac, environment: Java 14.0.2 (AdoptOpenJDK)"
)
public class PersonMapperImpl implements PersonMapper {
@Override
public Person toEntity(PersonRequest req) {
if ( req == null ) {
return null;
}
Person person = new Person();
person.setCustomName( req.getPersonName() );
return person;
}
}
2. MapStruct 다음 Lombok: MapperImpl 이 builder 로 생성한다.
먼저 실행된 MapStruct가 접근할 수 있는 getter/setter가 없는 상태라 생성에 실패한다. 그 다음 Lombok이 getter/setter 를 만든다.
다음 라운드에서 Lombok이 builder를 만들고, MapStruct가 다시 생성하려할텐데 이때는 builder가 있는 상태라 builder로 생성을 시도한다.
implementation "org.projectlombok:lombok:1.18.12"
implementation "org.mapstruct:mapstruct:1.3.1.Final"
// Changed order
annotationProcessor "org.mapstruct:mapstruct-processor:1.3.1.Final"
annotationProcessor "org.projectlombok:lombok:1.18.12"
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2021-02-10T14:23:34+0900",
comments = "version: 1.3.1.Final, compiler: javac, environment: Java 14.0.2 (AdoptOpenJDK)"
)
public class PersonMapperImpl implements PersonMapper {
@Override
public Person toEntity(PersonRequest req) {
if ( req == null ) {
return null;
}
PersonBuilder person = Person.builder();
person.customName( req.getPersonName() );
return person.build();
}
}
이 때, 문제가 될 만한건 뭘까?
사용자는 setCustomName 이라는 Setter 를 사용하기 위해 구현해놓았다. Mapper 인터페이스를 만들때도 Setter가 있으니 문제가 없다고 생각할 것이다. 하지만 Mapper가 Builder 메소드를 사용하여 코드를 생성할 때 문제가 된다. 주석친 부분이 없어 컴파일 에러가 발생하기 때문이다.
우선, 기본적으로 MapStruct 는 Target 객체에 @Builder 어노테이션이 달려있다면 Builder 메소드를 우선 사용하게 되어있다. 하지만 위의 이슈 때문에 제대로 작동되지 않았고 컴파일 에러까지 난 상황이다.
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Person {
private String name;
@JsonProperty("name")
public void setCustomName(String name) {
this.name = "custom = " + name;
}
@JsonProperty("name")
public String getCustomName() {
return this.name;
}
// public static class PersonBuilder {
// public PersonBuilder customName(String name) {
// return this.customName(name);
// }
// }
}
어떻게 해결해야 할까?
우선 Lombok 1.18.16 미만 버전을 사용할 경우 해결방법이다.
- (예시로 든 케이스 한정이긴 하나...) @Mapper 어노테이션에 값을 주는 방법이 있다.
- Lombok이 달려있는 객체와 Mapper 인터페이스 클래스를 각각 다른 모듈에 위치시킨다.
- 난 같은 모듈에 있어야 하고 순서 신경쓰기 귀찮다? -> 커스텀한 Setter & Builder 메소드 둘 다 항상 작성한다.
// 1번 방법
@Mapper(builder = @Builder(disableBuilder = true))
public interface PersonMapper {
Lombok 1.18.16 버전 이상을 사용할 경우 해결방법이다.
lombok-mapstruct-binding 의존성을 추가해준다. 이는 Lombok과 MapStruct가 함께 잘 동작하도록 만들어준다.
implementation "org.mapstruct:mapstruct:1.3.1.Final"
implementation "org.projectlombok:lombok:1.18.16"
implementation 'org.projectlombok:lombok-mapstruct-binding:0.2.0'
// 이제 순서 상관없음
annotationProcessor "org.projectlombok:lombok-mapstruct-binding:0.2.0"
annotationProcessor "org.mapstruct:mapstruct-processor:1.3.1.Final"
annotationProcessor "org.projectlombok:lombok:1.18.16"
오류가 있거나 틀린 부분이 있다면 태클 감사히 받겠습니다!