작업하면서 배우는 것들

MapStruct

Jungsoomin :) 2021. 1. 12. 22:42

Compile 사이클에 인터페이스에 선언 된 어노테이션을 기반으로 매핑 클래스를 Spring Bean 으로 만든다.

  • 사용하는 이유는 Model 클래스 사이의 맵핑을 간단하게 구현하기 위함이다.
  • Spring Bean 의 SingleTon 스코프로 자원 사용을 줄인다.
  • 간단한 어노테이션으로도 매핑 구현클래스를 생성해낸다.
  • 싱글톤이라는 점, 자동 주입이 가능하다는 점이 강점으로 보인다.

필요의존

  • org.mapstruct:mapstruct:${version}
  • org.mapstruct:mapstruct-processor:${version}
  • test,compile 시점에 어노테이션 프로세서를 사용해야 함
ext{
    mapstructVersion="1.4.1.Final"
}

    implementation "org.mapstruct:mapstruct:${mapstructVersion}"
    compileOnly "org.mapstruct:mapstruct-processor:${mapstructVersion}"
    annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"
    testAnnotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"

 


매핑예시

  • Api 클래스 , Enity 클래스로 나뉜 상태
  • 인터페이스로 MapStruct 가 구현할 클래스의 맵핑 방법을 어노테이션으로 기술
@AllArgsConstructor
@Getter
@Setter
public class Recommendation {
    private int productId;
    private int recommendationId;
    private String author;
    private int rate;
    private String content;
    private String serviceAddress;

    public Recommendation() {
        this.productId = 0;
        this.recommendationId = 0;
        this.author = null;
        this.rate = 0;
        this.content = null;
        this.serviceAddress = null;
    }
}

// Entity
@Document(collection = "recommendations")
@CompoundIndex(name = "prod-rec-id",unique = true,def = "{'productId':1, 'recommendationId':1}")
@Getter
@Setter
@NoArgsConstructor
@RequiredArgsConstructor
public class RecommendationEntity {
    @Id
    private String id;

    @Version
    private Integer version;

    @NonNull
    private int productId;
    @NonNull
    private int recommendationId;
    @NonNull
    private String author;
    @NonNull
    private int rating;
    @NonNull
    private String content;
}

 


MapStruct 예시

  • @MappercomponentModel 을 spring 으로 잡으면 SpringBean 을 생성
  • @Mappings매핑 주의사항을 기술
    • target = 리턴필드
    • ignore = 금지 여부
    • source = target 필드에 매핑시킬 필드 ( 매개변수로 접근 )
  • 이후 JavaCompile 사이클 실행
@Mapper(componentModel = "spring")
public interface RecommendationMapper {

    @Mappings({
            @Mapping(target = "rate",source = "entity.rating"),
            @Mapping(target = "serviceAddress", ignore = true)
    })
    Recommendation entityToApi(RecommendationEntity entity);

    @Mappings({
            @Mapping(target = "rating", source = "api.rate"),
            @Mapping(target = "id", ignore = true),
            @Mapping(target = "version", ignore = true)
    })
    RecommendationEntity apiToEntity(Recommendation api);

    List<Recommendation> entityListToApiList(List<RecommendationEntity> entityList);
    List<RecommendationEntity> apiListToEntityList(List<Recommendation> api);
}

만들어 졌다.


 

MapStruct 가 만든 구현 클래스

  • List 맵핑은 기본적으로 지정해놓은 맵핑 메서드를 내부적으로 호출하고 있음.
    • 가만 보았을 때에는 메서드 시그니쳐를 따라가는 듯 함
@Component
public class RecommendationMapperImpl implements RecommendationMapper {
    public RecommendationMapperImpl() {
    }

    public Recommendation entityToApi(RecommendationEntity entity) {
        if (entity == null) {
            return null;
        } else {
            Recommendation recommendation = new Recommendation();
            recommendation.setRate(entity.getRating());
            recommendation.setProductId(entity.getProductId());
            recommendation.setRecommendationId(entity.getRecommendationId());
            recommendation.setAuthor(entity.getAuthor());
            recommendation.setContent(entity.getContent());
            return recommendation;
        }
    }

    public RecommendationEntity apiToEntity(Recommendation api) {
        if (api == null) {
            return null;
        } else {
            RecommendationEntity recommendationEntity = new RecommendationEntity();
            recommendationEntity.setRating(api.getRate());
            recommendationEntity.setProductId(api.getProductId());
            recommendationEntity.setRecommendationId(api.getRecommendationId());
            recommendationEntity.setAuthor(api.getAuthor());
            recommendationEntity.setContent(api.getContent());
            return recommendationEntity;
        }
    }

    public List<Recommendation> entityListToApiList(List<RecommendationEntity> entityList) {
        if (entityList == null) {
            return null;
        } else {
            List<Recommendation> list = new ArrayList(entityList.size());
            Iterator var3 = entityList.iterator();

            while(var3.hasNext()) {
                RecommendationEntity recommendationEntity = (RecommendationEntity)var3.next();
                list.add(this.entityToApi(recommendationEntity));
            }

            return list;
        }
    }

    public List<RecommendationEntity> apiListToEntityList(List<Recommendation> api) {
        if (api == null) {
            return null;
        } else {
            List<RecommendationEntity> list = new ArrayList(api.size());
            Iterator var3 = api.iterator();

            while(var3.hasNext()) {
                Recommendation recommendation = (Recommendation)var3.next();
                list.add(this.apiToEntity(recommendation));
            }

            return list;
        }
    }
}

 


 

사용하는 API 서비스

  • 두 모델 클래스 간의 바인딩굉장히 수월하게 진행 됨.
@RestController
@Slf4j
public class RecommendationServiceImpl implements RecommendationService {

    private final ServiceUtil serviceUtil;
    private final RecommendationRepository repository;
    private final RecommendationMapper mapper;

    @Autowired
    public RecommendationServiceImpl(ServiceUtil serviceUtil, RecommendationRepository repository, RecommendationMapper mapper) {
        this.serviceUtil = serviceUtil;
        this.repository = repository;
        this.mapper = mapper;
    }

    @Override
    public List<Recommendation> getRecommendations(int productId) {

        if(productId < 1) throw new InvalidInputException("Invalid productId: "+productId);

        List<RecommendationEntity> entityList = repository.findByProductId(productId);
        List<Recommendation> list = mapper.entityListToApiList(entityList);
        list.forEach(r -> r.setServiceAddress(serviceUtil.getServiceAddress()));

        log.debug("getRecommendations: response size: {}",list.size());
        return list;
    }