본문으로 바로가기

Mapstruct 활용시 Collection 매핑 방법

category 삽질 정리 2022. 2. 6. 22:16

현재 회사에서 Mapstruct를 전사 표준으로 활용하고 있는데, 이곳에서 처음 사용해보는 라이브러리라 아직 익숙치가 못하다 ㅠㅠ

회사에서 개발중에 고통받았던 삽질과정에서 공부했던 것들을 간단하게 정리해본다.

 

Mapstruct란 DTO와 Entity간에 Converting을 손쉽게 도와주는 라이브러리이다. 

MessageEntity toMessageEntity(Message message) {
    return MessageEntity
            .builder()
            .id(message.getId())
            .to(message.getTo())
            .body(message.getBody())
            .messageType(message.getMessageType())
            .status(message.getStatus())
            .createdDateTime(message.getCreatedDateTime())
            .build();
}

위와 같이 Builder 형태로 Converting 할수있지만 필드가 점점 많아지면 개발자의 실수가 나올수도 있고 반복적인 행위가 계속된다.

이같은 상황에 자동적으로 매핑해주는 아주 편리한?(글쎄...) 라이브러리라고 한다..

 

Mapstruct 소개가 본 글의 목적은 아니니 소개는 이정도로만 하고.. Collection 형태가 아닌 단순한 Entity, DTO을 매핑하는 예시를 살펴보자.

@Mapper
public interface CarMapper {
 
    CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
 
    @Mapping(source = "numberOfSeats", target = "seatCount")
    CarDto carToCarDto(Car car);
}

DTO와 Entity간에 property명이 다를 경우에는 @Mapping을 활용해 직접 property를 지정해줘야한다. 이정도는 공식문서든 구글링이든 조금만 검색하면 나오니 별 무리는 없었는데...

 

@Mapper(componentModel = "spring")
public interface OrderMapper extends EntityMapper<OrderResponse, Orders> {
    @Mapping(target = "calculatePrice", source = "totalCalculatePrice")
    @Mapping(target = "discountPrice", source = "totalDiscountPrice")
    @Mapping(target = "originalPrice", source = "totalPrice")
    @Mapping(target = "orderItems", source = "orderProducts")
    OrderResponse toDto(Orders order);
}

내가 실무에서 부딪혔던 코드인데 orderItems, orderProducts 매핑과정에서 문제가 발생했다.

  • OrderResponse내 List<OrderItemDto> orderItems
  • Orders내 List<OrderProduct> orderProducts

property 타입이 원시타입이 아니라 클래스인데 서로 다른 타입일 경우 내부적으로 Converting이 되지않는 문제였다.

이런 경우에 Mapstruct에서 제공하는 옵션이 있다고 하는데...

@Mapper(componentModel = "spring", uses = {
        OrderItemMapper.class,
        ProductMapper.class
})
public interface OrderMapper extends EntityMapper<OrderResponse, Orders> {
    @Mapping(target = "calculatePrice", source = "totalCalculatePrice")
    @Mapping(target = "discountPrice", source = "totalDiscountPrice")
    @Mapping(target = "originalPrice", source = "totalPrice")
    @Mapping(target = "orderItems", source = "orderProducts")
    OrderResponse toDto(Orders order);
}

@Mapper의 uses를 활용하면 된다. property내에서 내부적으로 Mapper를 활용하려고 할때 사용하면 된다.

OrderItemMapper는 아래 형태로 작업되어있다. 아래 코드가 내부적으로 동작한다고 보면 된다.

@Mapper(componentModel = "spring", uses = {
        ShippingInvoiceMapper.class,
        ProductMapper.class
})
public interface OrderItemMapper extends EntityMapper<OrderItemDto, OrderProduct> {

    @Mapping(target = "shippingInvoice", expression = "java(null)")
    OrderItemDto toDto(OrderProduct orderProduct);
}

 

검색해서 찾는게 어려울뿐 활용법은 간단하다..

 

개인적으로는 라이브러리에 의존하는 방법은 별로 선호하지 않는데 그 이유는 라이브러리 활용법에 대해 또 공부해야하기 때문에 그 리소스도 무시할수가 없다 ㅠㅠ 아직까지는 직접 세팅하는 작업에 익숙한 상태라 Builder 형태로 작업하고 있긴한데 Mapstruct에 좀 익숙해지면 적극적으로 활용해봐야겠다!