JPA 에서 Entity를 DTO로 DTO를 Entity 로 변환 해주는 2가지 라이브러리가 있습니다.
MapStruct와 ModelMapper 인데요.
이번 포스팅에서는 둘의 설정방법과 차이에 대해서 알아보도록 하겠습니다.
1. 간단한 비교
springboot와 jpa 관련 설정에 대해서는 제외를 하고 진행하겠습니다.
우선 간단하게 둘의 차이를 설명드리면 이렇습니다.
종류 | 설정방법 | 속도 |
ModelMapper | 간단 | ↓에 비해 느리다 |
MapStruct | 살짝복잡 | ↑에 비해 빠르다 |
2.설정
2.1 ModelMapper 설정
2.1.1 gradle 의존성 주입
implementation 'org.modelmapper:modelmapper:2.4.2'
2.1.2 ModelMapper Config Class 구현
import org.modelmapper.ModelMapper;
import org.modelmapper.convention.MatchingStrategies;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ModelConfig {
@Bean
public ModelMapper modelMapper() {
ModelMapper mapper = new ModelMapper();
mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.LOOSE);
return mapper;
}
}
2.1.3 Entity to DTO
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.modelmapper.ModelMapper;
Stream<User> userStream = userRepository.findAll();
List<UserDTO> = userStream.map(entity -> mapper.map(entity, UserDTO.class)).collect(Collectors.toList());
이처럼 간단한 설정만으로 (물론 LOOSE 설정의 경우) 활용할 수 있는 장점이 있습니다.
하지만 MapStruct 에 비해 성능(속도)이 떨어지며 컴파일 단계에서 오류를 처리할 수 없는 단점이 있습니다.
그리고 LOOSE 매핑 설정의 경우 userNm, userPhone 과 같이 공통된 prefix를 사용하는 경우 매핑이 제대로 이뤄지지 않는 단점이 있습니다. (prefix를 지울 경우 정상 매핑)
2.2 MapStruct 설정
2.2.1 gradle 의존성 주입
/**lombok 사용 시 추가*/
implementation group: 'org.projectlombok', name: 'lombok-mapstruct-binding'
/**map struct */
compileOnly "org.mapstruct:mapstruct:1.4.2.Final"
annotationProcessor "org.mapstruct:mapstruct-processor:1.4.2.Final"
2.2.2 공통 Mapper 처리를 위한 Generic interface 설정
public interface GenericMapper<DTO, Entity> {
DTO toDTO(Entity entity);
Entity toEntity(DTO dto);
ArrayList<DTO> toDtoList(List<Entity> list);
ArrayList<Entity> toEntityList(List<DTO> dtoList);
/**Null 값이 전달될 경우 변화 시키지 않도록 설정 */
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
void updateFromDto(DTO dto, @MappingTarget Entity entity);
}
2.2.3. mapper 설정
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper(componentModel="spring")
public interface UserMapper extends GenericMapper<UserDTO, User>{
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
}
이렇게 설정할 경우 MapStruct에서는 build 시 MapperImpl class를 자동으로 생성해 줍니다.
generated-sources/annotations/{mapper를 설정한 package명} 에 자동 새성 됩니다.
해당 경로로 이동해서 파일을 열어보시면 DTO to Entity와 Entity to DTO 관련된 메소드들이 자동으로 추가 되어있는 것을 확인하실 수 있습니다.
하지만 명칭이 같지 않은 경우, 예를들어 Entity class에 @EmbeddedId 를 사용하여 복수키를 설정할 경우 매핑이 정상적으로 동작하지 않아 자동으로 생성된 매퍼에 객체 매핑 코드가 추가되지 않습니다.
이런 경우 매퍼 코드에 @Mapping anntation을 사용하여 별도로 매핑을 하주어야 합니다.
추가로 Entity나 DTO에 Setter, Getter 메소드가 없으면 제대로 매핑이 되지 않으니 꼭 추가해주세요.
imple에서도 해당 메소드들을 활용하여 코드가 자동생성 됩니다.
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
@Mapper(componentModel="spring")
public interface UserMapper extends GenericMapper<UserDTO, User>{
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
/** 추가 설정*/
@Mappings({
@Mapping(source= "UserAuthKey.systemCode", target = "systemCode"),
@Mapping(source= "systemInfo.systemName", target = "systemName")
})
AuthDTO userAuthToAuthDTO(UserAuth userAuth);
}
source가 매개변수로 전달받은 UserAuth 의 컬럼 경로, target은 AuthDTO의 변수명으로 설정해 주시면
해당 값이 매핑이 되게 됩니다.
기존에 자동으로 생성된 mapperImpl을 보시면 객체만 생성하던 코드에
setter가 생긴 것도 확인하실 수 있습니다.
Entity와 DTO는 아래의 코드를 참고하세요!
User Entity
@Entity
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "m_op_user", schema = "carbon")
public class USER implements Serializable {
private static final long serialVersionUID = 1L;
@Column(name = "user_id")
@Id
private String userId;
@Column(name = "user_nm")
private String userNm;
@JoinColumn(name = "user_id", referencedColumnName = "user_id")
@OneToMany
private Set<UserAuth> authorities;
}
UserAuth Entity
@Getter
@Setter
@Entity
@Table(name="m_op_user_auth_mpng", schema = "carbon")
public class UserAuth {
@EmbeddedId
private UserAuthKey userAuthKey;
@JoinColumn(name = "system_code", insertable = false, updatable = false)
@OneToOne
private System systemInfo;
}
UserAuthKey
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class UserAuthKey implements Serializable {
private static final long serialVersionUID = 1L;
@Column(name="user_id")
private String userId;
@Column(name="system_code", insertable = false, updatable = false)
private String systemCode;
@Column(name="auth_code")
private String authCode;
}
System Entity
@Entity
@Getter
@Setter
@ToString
@Table(name="m_op_system", schema = "carbon")
public class System {
@Column(name="system_code")
@Id
private String systemCode;
@Column(name="system_name")
private String systemName;
}
2.2.4 Entity to DTO
ArrayList<UserDTO> user = UserMapper.INSTANCE.toDtoList(userRepository.findAll());
ModelMapper와 MapStruct의 차이를 간단하게 알아보았습니다.
개인적으로 간단하고 개발기간이 짧은 프로젝트는 ModelMapper를 사용하고, 성능적 이슈가 있을 것으로 판단되는 프로젝트는 MapStuct를 쓰는 것이 어떨까 합니다.
'Programing > JPA' 카테고리의 다른 글
JPA Sequence 자동증가 설정 @SequenceGenerator (0) | 2023.03.07 |
---|---|
JPA MapStruct Date format 설정 timestamp, date, string (0) | 2023.02.07 |
JPA with Springboot, @convert date type to string or reverse (0) | 2021.05.11 |
JPA with Springboot, 저장, 수정, 삭제 방법, RUD (1) | 2021.05.07 |
JPA with Springboot, 조건 조회, Specification, Predicate, CriteriaBuilder (2) | 2021.05.04 |
댓글