본문 바로가기
📕 Spring Framework/Spring Project

객체 간 매핑을 위한 MapStruct 사용 방법

by GroovyArea 2022. 8. 29.
기존 프로젝트에서 Dto Entity를 매핑할 때 model mapper 라이브러리를 사용했었다. 

편하게 사용할 수 있었지만
내부적으로 리플렉션을 이용하기 때문에, 성능 상 문제가 있다.

이번에는 많이들 추천하는 Map Struct를 사용해봤다.  

적용하면서 자잘한 문제들이 있었는데, 내가 겪은 문제점들에 대한 해결 방법들을 정리해보겠다.

https://mapstruct.org/documentation/dev/reference/html/

 

MapStruct 1.5.2.Final Reference Guide

If set to true, MapStruct in which MapStruct logs its major decisions. Note, at the moment of writing in Maven, also showWarnings needs to be added due to a problem in the maven-compiler-plugin configuration.

mapstruct.org

=> 앞서 말하면 Map Struct 공식문서를 보면 대부분은 알 수가 있다. 

 

의존성 추가

implementation "org.mapstruct:mapstruct:1.5.2.Final"
compileOnly 'org.projectlombok:lombok'
annotationProcessor "org.mapstruct:mapstruct-processor:1.5.2.Final"
annotationProcessor 'org.projectlombok:lombok', "org.projectlombok:lombok-mapstruct-binding:0.2.0"

롬복과 같이 사용하면 충돌이 난다고는 하지만, 최신 버전에서는 그런게 없다고 한다. 

충돌을 피하려면 롬복 전에 추가하면 된다.

 

 

Generic Mapper 생성

public interface GenericDtoMapper<D, E> {

    D toDTO(E e);

    @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
    void updateFromVO(D dto, @MappingTarget E entity);
}
public interface GenericEntityMapper<D, E> {

    E toEntity(D d);

    @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
    void updateFromVO(D dto, @MappingTarget E entity);
}

=> 먼저 Entity 매퍼와 Dto 매퍼 인터페이스를 정의했다. 모든 타입을 받기 위해 Generic을 적용시켰다.

 

@Mapper(componentModel = "spring")
public interface UserJoinMapper extends GenericEntityMapper<JoinRequestDto, User> {
}

=> @Mapper 애노테이션을 통해 MapStruct가 구체화할 매퍼를 정의한다.

=> 이런 식으로 확장해 구체화하면 된다.

=> componentModel 이라는 애노테이션 속성을 통해 Spring Bean으로 등록해서 사용 가능하다.

 

 

 

문제점

=> 일반적인 필드들의 이름이 같거나 틀려도 @Mapping 애노테이션이나 @ValueMapping으로 가능한데,

참조 타입의 필드는 매핑에 좀 더 신경을 써줘야 한다.

 

@Mapping(target = "userId", source = "id")
@Override
DetailResponseDto toDTO(User user);

각 객체의 필드 이름이 틀릴 때 매핑 애노테이션 활용

 

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Builder
public class DetailResponseDto {
    
    private Long id;
    private String name;
    private String categoryName;
    private Integer price;
    private Integer quantity;
    private String content;
    @Setter
    private String image;
}

매핑할 DTO

 

@Entity
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Product extends BaseTimeEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    @JsonBackReference
    @ManyToOne
    @JoinColumn(name = "category_id")
    private Category category;

    private Integer price;

    private Integer quantity;

    private String content;

    private String image;

    @Enumerated(EnumType.STRING)
    @Column(name = "product_status", nullable = false)
    private ChickenStatus status;

매핑할 Entity

 

@Mapper(componentModel = "spring")
public interface ItemDetailMapper extends GenericDtoMapper<DetailResponseDto, Product> {

    @Mapping(target = "categoryName", source = "product.category.categoryName.chickenName")
    @Override
    DetailResponseDto toDTO(Product product);
}

매핑 인터페이스 정의 

카테고리는 Enum 타입이기 때문에, DTO 매핑 시 String으로 변경해야 한다. 

소스 객체인 Product 엔티티의 하위 필드들을. 연산자를 활용해 직접적으로 매핑한다.

 

대략적으로 매핑에 대한 문제들은 이렇게 해결할 수 있다.

반응형