Mapstruct 사용 시 collection 내부에 이름이 다른경우
web/Spring

Mapstruct 사용 시 collection 내부에 이름이 다른경우

반응형

Mapstruct를 통해 편하게 응답값을 매핑할 수 있도록 도와주는 라이브러리를 살펴본적 있다. https://wedul.site/703

 

만들면서 배우는 아키텍처 그리고 매핑 프레임워크 MapStruct를 사용한 매핑

만들면서 배우는 아키텍처 (Get Your Hands Dirty on Clean Architecture) 요새 읽던 책중에 'Get Your Hands Dirty on Clean Architecture' 책이 인상 깊었다. 원서로 팀원들과 스터디 하고 나서 인상깊어서 '만들면서 배

wedul.site

 

값을 매핑할 때 필드의 이름이 다를 경우에는 아래 처럼 Mapping애노테이션을 사용해서 source, target에 이름을 명시하여 이를 해결 할 수 있었다.

@Mapping(source = "address", target = "location")
@Mapping(source = "homeTel", target = "homeNumber")
AccountResponse domainToEntity(AccountEntity accountEntity);

 

하지만 collection을 사용하는 경우 collection 내부의 값의 이름이 다를경우에는 다르게 해결해줘야한다. 예를 들어 AccountEntity에서 AccountResponse로 값을 매핑한다고 가정하였을 때 내부에 images라는 응답값도 매핑한다고 가정해보자. 아래 처럼 AccountImageResponse와 AccountImageEntity 내부에 필드이름이 서로 다를경우 매핑이 정상적으로 되지 않는다. 아래 코드를 살펴보자.

package com.wedul.mapstructtest.dto;

import lombok.Builder;
import lombok.Getter;

import java.util.List;

@Getter
public class AccountResponse {

    private final String name;
    private final int age;
    private final String location;
    private final String homeNumber;
    private final List<AccountResponseImage> images;

    @Builder
    public AccountResponse(String name, int age, String location, String homeNumber, List<AccountResponseImage> images) {
        this.name = name;
        this.age = age;
        this.location = location;
        this.homeNumber = homeNumber;
        this.images = images;
    }
}


package com.wedul.mapstructtest.dto;

import lombok.Builder;
import lombok.Getter;

@Getter
public class AccountResponseImage {

    private long id;
    private String path;
    private String imageName;

    @Builder
    public AccountResponseImage(long id, String path, String imageName) {
        this.id = id;
        this.path = path;
        this.imageName = imageName;
    }
}


package com.wedul.mapstructtest.entity;

import lombok.Builder;
import lombok.Getter;

import java.util.List;

@Getter
public class AccountEntity {

    private String name;
    private int age;
    private String address;
    private String homeTel;
    private List<AccountImage> images;

    @Builder
    public AccountEntity(String name, int age, String address, String homeTel, List<AccountImage> images) {
        this.name = name;
        this.age = age;
        this.address = address;
        this.homeTel = homeTel;
        this.images = images;
    }
}


package com.wedul.mapstructtest.entity;

import lombok.Builder;
import lombok.Getter;

@Getter
public class AccountImage {

    private long id;
    private String path;
    private String name;

    @Builder
    public AccountImage(long id, String path, String name) {
        this.id = id;
        this.path = path;
        this.name = name;
    }
}

 

두 응답값을 매핑 하는 mapper를 아래처럼 생성하고 비교하는 테스트를 돌려보면 값이 매핑되지 않아 테스트가 실패하는 걸 볼수 있다.

package com.wedul.mapstructtest.mapper;

import com.wedul.mapstructtest.dto.AccountResponse;
import com.wedul.mapstructtest.entity.AccountEntity;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

@Mapper(componentModel = "spring")
public interface AccountMapper {

    @Mapping(source = "address", target = "location")
    @Mapping(source = "homeTel", target = "homeNumber")
    AccountResponse domainToEntity(AccountEntity accountEntity);

}

// 테스트

package com.wedul.mapstructtest;

import com.wedul.mapstructtest.dto.AccountResponse;
import com.wedul.mapstructtest.dto.AccountResponseImage;
import com.wedul.mapstructtest.entity.AccountEntity;
import com.wedul.mapstructtest.entity.AccountImage;
import com.wedul.mapstructtest.mapper.AccountMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;
import java.util.stream.Collectors;

import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest
class AccountResponseMappingIntegrationTest {

    @Autowired
    private AccountMapper accountMapper;

    @Test
    void shouldAccountMappingTest() {
        // given
        List<AccountImage> imageEntities = List.of(AccountImage.builder()
                        .id(1L)
                        .path("path1")
                        .name("image1")
                        .build(),
                AccountImage.builder()
                        .id(2L)
                        .path("path2")
                        .name("image2")
                        .build());
        AccountEntity accountEntity = AccountEntity.builder()
                .age(10)
                .name("wedul")
                .address("seoul")
                .homeTel("02-1111-1111")
                .images(imageEntities)
                .build();

        // when
        AccountResponse response = accountMapper.domainToEntity(accountEntity);

        // then
        AccountResponse expected = AccountResponse.builder()
                .age(accountEntity.getAge())
                .homeNumber(accountEntity.getHomeTel())
                .location(accountEntity.getAddress())
                .name(accountEntity.getName())
                .images(imageEntities.stream().map(d -> AccountResponseImage.builder()
                        .path(d.getPath())
                        .imageName(d.getName())
                        .id(d.getId())
                        .build()).collect(Collectors.toList()))
                .build();
        assertThat(response).usingRecursiveComparison().isEqualTo(expected);
    }


}

값이 매핑 되지 않아 recursive equal 테스트 실패

단순하게 해결해보기 위해 @Mapper value로 아래와 같이 설정해봤으나 없는 property라는 오류를 보게되었다.

@Mapping(source = "images[].name", target = "images[].imageName")

 

collection내에 필드를 수정할 때는 property를 직접 수정하기 보다는 collection내부에 값을 매핑하기 위한 mapper를 하나더 생성해서 그 mapper를 지정해주면 되는데 @Mapper 애노테이션에서 uses를 사용해서 지정해주면 된다.

 

// 이미지용 Mapper
package com.wedul.mapstructtest.mapper;

import com.wedul.mapstructtest.dto.AccountResponse;
import com.wedul.mapstructtest.dto.AccountResponseImage;
import com.wedul.mapstructtest.entity.AccountEntity;
import com.wedul.mapstructtest.entity.AccountImage;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

@Mapper(componentModel = "spring")
public interface AccountImageMapper {

    @Mapping(source = "name", target = "imageName")
    AccountResponseImage domainToEntity(AccountImage accountEntity);

}



// 기존 Mapper에 내부 Collection image매핑을 위한 AccountImageMapper추가
package com.wedul.mapstructtest.mapper;

import com.wedul.mapstructtest.dto.AccountResponse;
import com.wedul.mapstructtest.entity.AccountEntity;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

@Mapper(componentModel = "spring", uses = {
        AccountImageMapper.class
})
public interface AccountMapper {

    @Mapping(source = "address", target = "location")
    @Mapping(source = "homeTel", target = "homeNumber")
    AccountResponse domainToEntity(AccountEntity accountEntity);

}

그 다음 테스트를 돌려보면 실패하던 테스트가 성공하는걸 볼 수 있다.

 

반응형