Entity와 DTO간의 객체 매핑시 사용하는 라이브러리로 ModelMapper와 Mapstruct가 있습니다.
기존에는 ModelMapper를 사용하고 있었지만 Mapstruct가 속도와 성능, 기능면에서 더 우수하다는 글을 보고 구글링하면서 리팩토링을 진행하게 되었습니다. 직접 사용한 부분만 정리하여서 부족한 내용이 많이 있습니다.
Mapstuct의 특징
- 컴파일 시 오류를 확인할 수 있다.
- 리플렉션(Reflction)을 사용하지 않기 때문에 매핑 속도가 빠르다. (ModelMapper는 런타임 시점에 매핑을 시도합니다.)
- 디버깅이 쉽다.
- 생성된 매핑 코드를 눈으로 직접 확인할 수 있다. (생성된 구현체로 직접 접근하여 확인할 수 잇습니다.)
참조)
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는 컴파일 시점에 매핑클래스를 생성해 해당 구현체를 런타임 시점에 사용하여 매핑함으로써 성능적으로 상당히 부담이 덜어집니다.
'Spring' 카테고리의 다른 글
[Spring] 웹 스코프(request scope) 와 Provider / 프록시 (0) | 2021.11.30 |
---|---|
[Spring] 빈 스코프(프로토타입 스코프 / prototype scope) (0) | 2021.11.29 |
[Spring] HiddenHttpMethodFilter로 GET,POST를 DELETE,PUT등 으로 받기_Spring Boot설정 (0) | 2021.08.26 |
[Spring] 유효성 검사(Validating Form) (0) | 2021.08.18 |
[Spring] HTTP 메시지 컨버터 (HttpMessageConverter) (0) | 2021.08.02 |