ObjectMapper와 MapStruct

이유진·2023년 12월 17일
post-thumbnail

최근에 ? DTO ➡️ A Entity 로 변환해야 하는 로직을 개발해야 했다.

단순하게 하나의 DTO에서 하나의 Entity로 변환하는 것이었다면 쉬웠겠지만, 다양한 형태의 DTO를 하나의 Entity로 변환해야 했다.

이때 고려해본 것이 ObjectMapper와 MapStruct였는데, 고민 했던 내용을 작성해보려고 한다.

요구사항

? DTO ➡️ A Entity
? DTO의 depth는 1이상일 수 있다
? DTO의 속성명은 A Entity와 다를 수 있다
? DTO를 가공시켜야 A Entity로 변환이 가능할 수도 있다

일단 기본적으로 depth가 1이며, 속성명이 같은 DTO만 변환해보려고 한다

ObjectMapper

Jackson 라이브러리의 ObjectMapper 클래스 알아보기
➡️ https://www.baeldung.com/jackson-object-mapper-tutorial

ObjectMapper는 Json ↔️ Java 객체 간 변환할 때 사용한다.
ObjectMapper의 convertValue는 자바 객체 간 변환에 사용된다.

public class AEntityConverter {
    /**
     * DTO를 Entity로 변환
     */
    public static <T> AEntity convertToAEntity(T dto) {
        ObjectMapper objectMapper = new ObjectMapper();

        // dto -> JSON -> AEntity
        return objectMapper.convertValue(dto, AEntity.class);
    }
}

변환할 결과는 AEntity로 정해져 있으므로 AEntity.class를 넣어주고, 변환의 대상은 어떤 DTO 객체인지 알 수 없으므로 Generic type으로 넣어준다.

MapStruct

동아리, 인턴, 등등 프로젝트에서 MapStruct를 사용해 DTO to Entity, Entity to DTO 등의 변환을 해왔다.
그래서 좀 더 익숙하고 도입에 긍정적이었던 것 같다.

MapStruct를 처음 사용하려고 했을 때 많이 읽었던 포스트는 NHN 블로그의 글이었다.
최근엔 네이버 클라우드 플랫폼 글을 읽어보았다.

MapStruct를 사용할 경우, Mapper interface를 만들어 사용한다.

@Mapper
public interface AEntityMapper {
    AEntity toAEntity(BDTO bDto);
}

이렇게 interface에 메서드를 정의해주면, 빌드 시점에 구현체 코드가 생성된다.

DTO Depth가 2이상이면?

ObjectValue

BDTO의 형태가 아래와 같다고 해보자

public class BDTO {
	private String id;
    private CDTO beforeDTO;
    private CDTO afterDTO;
}

public class CDTO {
	private String name;
    private String email;
}

name, email 정보 변경을 beforeDTO, afterDTO로 나타낸다고 생각해보자.
이때 최신 데이터만 AEntity로 변환하고자 한다. (id + afterDTO)

ObjectMapper의 convertValue를 사용하여 Map으로 변환한다.

public class AEntityConverter {
    /**
     * DTO를 Entity로 변환
     */
    public static <T> AEntity convertToAEntity(T dto) {
        ObjectMapper objectMapper = new ObjectMapper();

        // dto -> JSON -> Map
        Map<String, Object> map = objectMapper.convertValue(dto, Map.class);
        
        // return MapToEntity; ??
    }
}

Map으로 변경하면 아래와 같이 변경될 것이다.

map = {
	"id": "id값",
    "beforeDTO": {"name": "이름1", "email": "email1"},
    "afterDTO": {"name": "이름2", "email": "email2"}
}

이것을 AEntity로 변환한다면?

// MapToEntity
// 타입 변환코드는 생략
AEntity.builder
	.id(map.id)
    .name(map.afterDTO.name)
    .email(map.afterDTO.email)
    .build();

이렇게 될 경우 문제는, map에서 일일히 name과 email의 위치를 알려줘야 한다는 것이다. 이러면 convert method에서 generic을 쓰는 이점이 사라진다.

MapStruct

@Mapper
public interface AEntityMapper {

    @Mapping(source = "afterDTO.name", target = "name")
    @Mapping(source = "afterDTO.email", target = "email")
    AEntity toAEntity(BDTO bdto);
}

좀 더 간결하고 코드 짜는 시간이 줄어든다. 물론 MapStruct도 일일히 source를 적어주어야 한다.

그래서 어떤걸 사용해야 할까요?

일단 depth 1에 대해서는 objectMapper + generic으로 한번에 처리하도록 하고, depth가 2이상이거나 전처리가 필요한 DTO는 일일히 mapping 방법을 정해주도록 했다.

더 좋은 방법이 있으면 댓글로 알려주세요 😊

profile
BackEnd Developer

0개의 댓글