Spring

[Spring] ModelMapper 대신 Mapstruct 사용하기

cornarong 2021. 10. 17. 21:35

Entity와 DTO간의 객체 매핑시 사용하는 라이브러리로 ModelMapperMapstruct가 있습니다.

 

기존에는 ModelMapper를 사용하고 있었지만 Mapstruct가 속도와 성능, 기능면에서 더 우수하다는 글을 보고 구글링하면서 리팩토링을 진행하게 되었습니다. 직접 사용한 부분만 정리하여서 부족한 내용이 많이 있습니다.

 

Mapstuct의 특징

  1. 컴파일 시 오류를 확인할 수 있다.
  2. 리플렉션(Reflction)을 사용하지 않기 때문에 매핑 속도가 빠르다.  (ModelMapper는 런타임 시점에 매핑을 시도합니다.)
  3. 디버깅이 쉽다.
  4. 생성된 매핑 코드를 눈으로 직접 확인할 수 있다. (생성된 구현체로 직접 접근하여 확인할 수 잇습니다.)

 

참조)

 

Java - Model(Object) mapping을 위한 Mapstruct (맵스트럭트)!

오늘 다루어볼 내용은 Model mapping을 아주 쉽게 해주는 Mapstruct라는 라이브러리를 다루어볼 것이다. 그 전에 Model mapping에 많이 사용되는 ModelMapper와의 간단한 차이를 이야기해보면, Model Mapper는 Ru..

coding-start.tistory.com

 

Object Mapping 어디까지 해봤니? : NHN Cloud Meetup

이 글에서는 Object Mapping 라이브러리인 MapStruct에 대해 소개합니다. NHN Forward 2019에서 발표한 내용에 대해 조금 더 자세히 설명합니다.

meetup.toast.com

 

 

1. gradle 의존성 추가. 

	// 2. mapstruct
	implementation 'org.mapstruct:mapstruct:1.4.2.Final'
	annotationProcessor "org.mapstruct:mapstruct-processor:1.4.2.Final"
    
    	// lombok의 Builder와 같이 사용할 경우 추가.
	compileOnly 'org.projectlombok:lombok:1.18.22'
	annotationProcessor 'org.projectlombok:lombok:1.18.22'
	annotationProcessor 'org.projectlombok:lombok-mapstruct-binding:0.2.0'

 

2. Mapper 인터페이스 구현

저는 User와 UserDto을 사용하여 양방향으로 설정 해보았습니다.

 

기존은 1번 처럼 설정해주면 됩니다.

2번의 경우는 커스텀이 필요한 상황이 생겨서 직접 구현체를 구현하였습니다.

(직접 커스텀하여 구현할 경우  매핑 구현체가 생성되지 않습니다.)

 

아래의 사용된 Mapping 어노테이션의 파라미터는 지원하는 종류가 정말 많아서 필요에 따라 구글링 해보면 좋겠습니다.

import com.study.spring.form.UserDto;
import com.study.spring.model.User;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

@Mapper
public interface UserMapper {

    /* 1. UserDto -> User */
    @Mapping(target = "id", ignore = true)
    @Mapping(target = "profile", ignore = true)
    @Mapping(target = "address", ignore = true)
    User toUserEntity(UserDto userDto);

   /* User -> UserDto */
    @Mapping(target = "address", ignore = true)
    default UserDto toUserDto(User user) {
        UserDto userDto = UserDto.builder()
                .id(user.getId())
                .uid(user.getUid())
                .name(user.getName())
                .nickname(user.getNickname())
                .phone(user.getPhone())
                .birth(String.valueOf(user.getBirth()))
                .gender(user.getGender())
                .email(user.getEmail())
                .postcode(user.getAddress().getPostcode())
                .address(user.getAddress().getAddress())
                .detailAddress(user.getAddress().getDetailAddress())
                .sinceUseDto(String.valueOf(user.getSince()))
                .roleUseDto(user.getRoles().get(0).getName())
                .build();
        if(user.getProfile() == null){
            userDto.setProfileUseDto("noImage.jpg");
            userDto.setProfilePathUseDto("");
        }else {
            userDto.setProfileUseDto(user.getProfile());
            userDto.setProfilePathUseDto(user.getProfilePath());
        }
        return userDto;
    }
}

 

3. 생성된 구현체

아래의 코드는 1번의 구현체 입니다.

2번의 경우는 직접 커스텀하였기에 따로 구현체가 생성되지 않고 커스텀한 구현체를 호출합니다.

import com.study.spring.form.UserDto;
import com.study.spring.model.User;
import java.time.LocalDate;
import javax.annotation.processing.Generated;

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2021-10-17T20:44:55+0900",
    comments = "version: 1.3.1.Final, compiler: javac, environment: Java 11.0.11 (Oracle Corporation)"
)
public class UserMapperImpl implements UserMapper {

    @Override
    public User toUserEntity(UserDto userDto) {
        if ( userDto == null ) {
            return null;
        }

        User user = new User();

        user.setUid( userDto.getUid() );
        user.setPassword( userDto.getPassword() );
        user.setName( userDto.getName() );
        user.setNickname( userDto.getNickname() );
        if ( userDto.getBirth() != null ) {
            user.setBirth( LocalDate.parse( userDto.getBirth() ) );
        }
        user.setPhone( userDto.getPhone() );
        user.setGender( userDto.getGender() );
        user.setEmail( userDto.getEmail() );

        return user;
    }
}

 

4. 사용하기

  /* User -> UserDto */
  UserMapper userMapper = new UserMapperImpl();
  User user = userMapper.toUserEntity(userDto);
  
  /* UserDto -> User */
  UserMapper userMapper = new UserMapperImpl();
  UserDto userDto = userMapper.toUserDto(findUser);

 

정리)

ModelMapper는 런타임 시점에 리플렉션으로 매핑을 하게 됩니다.

-> 이 부분이 가장 큰 단점으로 성능에 많은 부담이 된다고 합니다.

 

Mapstruct는 컴파일 시점에 매핑클래스를 생성해 해당 구현체를 런타임 시점에 사용하여 매핑함으로써 성능적으로 상당히 부담이 덜어집니다.