😋 MapStruct 사용기
객체 간의 간단한 매핑을 위해서 MapStruct
를 도입한 이야기를 해보려고 합니다.
어떻게 사용하는지와 사용하고 어땠는지 적어보겠습니다.
🪄 MapStruct란?
Java 기반의 객체 매핑 라이브러리입니다. 다음과 같은 특징을 가지고 있습니다.
- 컴파일 시점에 매핑 코드를 작성한다. 그래서 런타임 시 오버헤드가 없고 안전하다.
- 다른 매핑 라이브러리보다 빠르다.
- 어노테이션을 통해서 매핑을 개발자가 쉽게 제어할 수 있다.
- Lombok에 대한 의존성이 꼭 필요하다.
💡 MapStruct 사용법
예시에 사용될 DTO
와 Entity
는 다음과 같습니다.
// DTO 1번
public class RequestDto {
private String title;
private String content;
}
// DTO 2번
public class ResponseDto {
private String title;
private String content;
}
// DTO 3번
public class MapDto { // map 모양이라 MapDto zz
private String key;
private String value;
}
// DTO 4번
public class DoubleDto { // dto 두개라 DoubleDto zzz
private String title;
private String content;
private String key;
private String value;
}
1️⃣ 기본 사용법
인터페이스에 @Mapper
을 달아주면 컴파일 시점에서 해당 인터페이스의 구현체를 자동으로 만들어줍니다.
스프링에서 사용할 경우 componentModel = "spring"
옵션을 추가해 구현체가 빈으로 등록되도록 합니다.
다음 예시와 같이 작성하면 되고 필드가 같다면 따로 작업을 해줄 필요 없이 알아서 매핑이 됩니다.
import org.mapstruct.Mapper;
@Mapper(componentModel = "spring")
public interface MyMapper {
ResponseDto mapFrom(RequestDto requestDto);
}
2️⃣ 이름이 다른 필드 매핑하기
필드가 다른 경우 매핑을 하기 위해선 다음과 같이 @Mapping
을 사용해서 명시해줘야합니다.
@Mapper(componentModel = "spring")
public interface MyMapper {
// ...
@Mapping(source = "key", target = "title")
@Mapping(source = "value", target = "content")
ResponseDto mapFrom(MapDto mapDto);
}
3️⃣ 두 객체를 한 객체로 매핑하기
두 개의 객체로 한 객체로도 매핑해 줄 수 있습니다. 예시는 다음과 같습니다.
@Mapper(componentModel = "spring")
public interface MyMapper {
// ...
DoubleDto mapFrom(RequestDto requestDto, MapDto mapDto);
}
4️⃣ 사용자 정의 메서드
개발자가 직접 매핑을 정의할 수도 있습니다.
default
키워드를 사용해서 더욱 복잡한 로직을 구현해서 매핑할 수 있습니다.
개인적으로 이 방법은 간결함이 핵심인 MapStruct를 사용하는 이유가 없다고 생각합니다.
@Mapper(componentModel = "spring")
public interface MyMapper {
// ...
default ResponseDto mapFrom(RequestDto requestDto) {
return new ResponseDto(requestDto.getTitle(), requestDto.getContent());
}
}
5️⃣ 심화 사용법
필드의 개수가 다른 객체들을 매핑하고 싶을 수도 있고, null
을 의도할 수도 있습니다.
MapStruct
는 이런 다양한 경우를 지원합니다.
다음과 같이 파라미터를 필드에 매핑하고 들어올 null
을 대비해 default
로 설정해 줄 수 있습니다.
@Mapper(componentModel = "spring")
public interface MyMapper {
// ...
@Mapping(source = "text", target = "content", defaultValue = "content")
ResponseDto mapFrom(String title, String text);
}
ignore = true
옵션을 통해서 특정 필드를 매핑하지 않도록 설정할 수 있습니다. 특징은 다음과 같습니다.
Target
의 타입에 따라 기본 초기값으로 초기화된다. (ex. 객체 : null, 숫자형 : 0)- 필드가 달라도 무시할 수 있다. 즉,
Target
에Source
에 매핑할 필드가 없더라도 무시할 수 있다.
@Mapper(componentModel = "spring")
public interface MyMapper {
// ...
@Mapping(target = "content", ignore = true)
ResponseDto mapFrom(String title);
}
Target
의 특정 필드가 시간, 난수 등과 같이 만들어지는 값이라면 expression
을 사용해서 매핑할 수 있습니다.
Source
의 없는 값이어도 expression
으로 초기값으로 설정해 줄 수 있습니다.
여기서 java()
는 Mapstruct
의 키워드로 java 코드를 실행시켜 주는 메서드입니다.
@Mapper(componentModel = "spring")
public interface MyMapper {
// ...
@Mapping(target = "content", expression = "java(getContent())")
ResponseDto mapFrom(String title);
default String getContent() {
return "defaultContent";
}
}
반복되는 연산을 처리하기 위해서 메서드를 만들어서 사용할 수도 있습니다.
메서드의 이름을 @Named
로 매핑해 주고, qualifiedByName
에 명시해 사용할 수 있습니다.
expression
과 다르게 없는 필드에 대해서 매핑하지 못합니다.
@Mapper(componentModel = "spring")
public interface MyMapper {
@Mapping(source = "num", target = "key", qualifiedByName = "toKey")
MapDto mapFrom(int num, String value);
@Named("toKey")
static String toKey(int num) {
switch (num) {
case 1:
return "key1";
case 2:
return "key2";
default:
return "defaultKey";
}
}
}
😎 적용 후기
사용하기로 마음먹은 것은 다음 이유들 때문입니다.
Service
에 존재하는DTO
와의 의존성이 너무 불편했다.- 객체가 변환되는 로직이 너무 퍼져있다.
DTO
와Entity
사이 의존성이 발생한다.
이를 해결하기 위해서 MapStruct
를 도입했고, 현재 의존성을 모두 분리했습니다. 사실 제대로 했는지는 모름 ㅎㅎ..!
다음에 어떻게 사용했고, 의존성을 분리했는지, 그리고 어떤 문제가 있었는지 모두 소개해보도록 하겠습니다.
어떤 라이브러리이던 추가적인 학습을 요구하니 필요한 상황인지 확인하고 도입하도록 합시다 😋😋😋
'Web > BackEnd' 카테고리의 다른 글
[Backend] DB 인덱스 이해하기 (0) | 2023.08.04 |
---|---|
[Backend] 트랜잭션, 격리 수준 (0) | 2023.07.27 |
[BackEnd] Enum 유효성 검사 구현기 (1) | 2023.04.25 |
[AWS] AWS 인프라 구축하기 - CodeDeploy (5) (0) | 2023.04.05 |
[AWS] AWS 인프라 구축하기 - SES (4) (0) | 2023.04.05 |
댓글