Programing/JPA

JPA MapStruct, ModelMapper 설정 방법, 차이 Entity to DTO, DTO to Entity

리커니 2023. 2. 3. 16:51
반응형

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를 쓰는 것이 어떨까 합니다.

반응형