본문 바로가기
Web/BackEnd

[BackEnd] MapStruct 사용기

by 희조당 2023. 5. 13.
728x90

😋 MapStruct 사용기

객체 간의 간단한 매핑을 위해서 MapStruct를 도입한 이야기를 해보려고 합니다.

어떻게 사용하는지와 사용하고 어땠는지 적어보겠습니다.


🪄 MapStruct란?

Java 기반의 객체 매핑 라이브러리입니다. 다음과 같은 특징을 가지고 있습니다.

  • 컴파일 시점에 매핑 코드를 작성한다. 그래서 런타임 시 오버헤드가 없고 안전하다.
  • 다른 매핑 라이브러리보다 빠르다.
  • 어노테이션을 통해서 매핑을 개발자가 쉽게 제어할 수 있다.
  • Lombok에 대한 의존성이 꼭 필요하다.

💡 MapStruct 사용법

예시에 사용될 DTOEntity는 다음과 같습니다.

// 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)
  • 필드가 달라도 무시할 수 있다. 즉, TargetSource에 매핑할 필드가 없더라도 무시할 수 있다.
@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와의 의존성이 너무 불편했다.
  • 객체가 변환되는 로직이 너무 퍼져있다.
  • DTOEntity 사이 의존성이 발생한다.

이를 해결하기 위해서 MapStruct를 도입했고, 현재 의존성을 모두 분리했습니다. 사실 제대로 했는지는 모름 ㅎㅎ..!

다음에 어떻게 사용했고, 의존성을 분리했는지, 그리고 어떤 문제가 있었는지 모두 소개해보도록 하겠습니다.

 

어떤 라이브러리이던 추가적인 학습을 요구하니 필요한 상황인지 확인하고 도입하도록 합시다 😋😋😋

댓글