[Spring Boot] MapStruct

끝으로 ㅣ 2024. 4. 26. 20:56

Java 객체 간의 매핑을 도와주는 라이브러리인 MapStruct에 대해 알아보자.

 

MapStruct

MapStruct는 Java Bean 유형 간의 매핑 구현을 단순화해주는 라이브러리로써 다음과 같은 장점을 가지고 있다.

  • 컴파일 시점에 코드를 생성하여 런타임 안정성 보장
  • 다른 매핑 라이브러리보다 빠르다
  • 어노테이션을 사용한 선언적 개발로 구현이 쉽다

 

MapStruct 의존성 추가

implementation("org.mapstruct:mapstruct:1.5.3.Final")
annotationProcessor("org.mapstruct:mapstruct-processor:1.5.3.Final")

 

Mapper 인터페이스 구성

componentModel = "spring"

- Spring 컨테이너에 Bean 등록

 

unmappedTargetPolicy

    ReportingPolicy.IGNORE - Target에 매핑되지 않는 필드 무시

    ReportingPolicy.ERROR - Target에 매핑되지 않는 필드 ERROR 발생

    ReportingPolicy.WARN - Target에 매핑되지 않는 필드 WARN 발생

 

nullValueMappingStrategy, nullValueIterableMappingStrategy

    NullValueMappingStrategy.RETURN_DEFAULT - Source가 null인 경우 Target을 default 값으로 설정

    NullValueMappingStrategy.RETURN_NULL - Source가 null인 경우 Target을 null로 설정

 

Source Class와 Target Class의 필드명이 동일하지 않은 경우 @Mapping 어노테이션을 사용해서 필드명을 선언

 

매핑하려는 필드가 객체인 경우 . 을 사용해서 객체 하위 필드에 대한 매핑

 

@Mapping 어노테이션의 qualifiedByName 속성과 @Named 어노테이션을 사용하면 매핑될 값에 대해 Custom 메소드로 변환이 가능

 

@Mapping 어노테이션의 defaultValue, defaultExpression, Ignore를 사용해서 default 값을 설정하거나 제외 가능

 

@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface PostMapper {      
 
    // BoardPostSearchDto를 PostSearchDto로 매핑해주는 메소드
    @Mapping(source = "fixPosts", target = "fixList", qualifiedByName = "mapCreatedAt")
    @Mapping(source = "posts.content", target = "list", qualifiedByName = "mapCreatedAt")
    @Mapping(source = "posts.number", target = "pageIndex", qualifiedByName = "setIndexFromOne")
    @Mapping(source = "posts.totalPages", target = "totalPage")
    @Mapping(source = "posts.size", target = "pageSize")
    @Mapping(source = "posts.totalElements", target = "totalCount")
    PostSearchDto postFromBoard(BoardPostSearchDto boardPostSearchDto);
 
    // API 서버 응답의 날짜 포맷 변경
    @Named("mapCreatedAt")
    default List<BoardPostListDto> mapCreatedAt(List<BoardPostListDto> boardPostListDtoList) {
        if (ObjectUtils.isEmpty(boardPostListDtoList)) {
            return null;
        }
        return boardPostListDtoList.stream()
                .map(boardPostListDto -> {
                    String convertedCreatedAt = convertDateFormat(boardPostListDto.getCreatedAt());
                    boardPostListDto.setCreatedAt(convertedCreatedAt);
                    return boardPostListDto;
                })
                .collect(Collectors.toList());
    }
 
    // API 서버 응답의 날짜 포맷 변경
    @Named("mapCreatedAt")
    default String convertDateFormat(String inputDate) {
        SimpleDateFormat inputFormat = new SimpleDateFormat("yyyyMMddHHmmss");
        SimpleDateFormat outputFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
        try {
            return outputFormat.format(inputFormat.parse(inputDate));
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }
 
    // 노출되는 페이징 인덱스 조정
    @Named("setIndexFromOne")
    default int setIndexFromOne(int pageIndex) {
        return pageIndex + 1;
    }
}

다음과 같이 PostMapper 인터페이스를 구성해서 필드명이 다른 필드 간에도 매핑이 가능하고, Custom 메소드 적용도 가능하다.

 

프로젝트를 진행하며 유용하게 사용했던 라이브러리인 MapStruct에 대해 알아보았다.

외부 API를 사용하여 객체 간 매핑이 많이 이뤄질 경우 사용해 보는 것도 좋을 것이다.