본문 바로가기
프로그래밍/Java

ModelMapper란?

by KHRDev 2023. 7. 12.

Java로 Object field mapping시 자주 사용되는 ModelMapper에 대해 알아보자~🤔


Java에서 vo <-> dto, dto <-> entity 매핑 시 유용하게 사용되는 라이브러리로는 점유율이 높은 ModelMapper와 MapStruct가 존재한다.

 

이번 글에선 ModelMapper에 대해 알아보고 추후 MapStruct에 대해 알아보도록 하자.


1. ModelMapper 의존성 추가

maven 사용자의 경우 

<dependency>
    <groupId>org.modelmapper</groupId>
    <artifactId>modelmapper</artifactId>
    <version>3.0.0</version>
</dependency>

gradle 사용자의 경우(kotlin)

implementation("org.modelmapper:modelmapper:3.0.0")

현 시점에서 3.1.1 버전까지 나온 상태이므로 참고하면 될 것 같다.


2. ModelMapper Mapping strategy

ModelMapper의 매핑 전략은 3가지가 존재한다. loose, standard, strict

mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.LOOSE);
mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STANDARD);
mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);

매핑 전략을 설정하지 않았을 땐 standard가 default이다.

ModelMapper의 공식 문서에서는 다음과 같이 설명하고 있다.

 

STANDARD

표준 일치 전략을 사용하면 소스 속성을 대상 속성에 지능적으로 일치시킬 있으므로 모든 대상 속성이 일치하고 모든 소스 속성 이름에 하나 이상의 토큰이 일치해야 합니다다음 규칙이 적용됩니다.

  • 토큰은 어떤 순서로든 일치시킬 수 있습니다.
  • 모든 대상 속성 이름 토큰이 일치해야 합니다.
  • 모든 소스 속성 이름에는 하나 이상의 토큰이 일치해야 합니다.

표준 일치 전략은 기본적으로 구성되어 있으며 정확하지는 않지만 대부분의 시나리오에서 사용하기에 이상적입니다.

 

LOOSE

느슨한 일치 전략을 사용하면 계층 구조의 마지막 대상 속성  일치하도록 요구하여 소스 속성을 대상 속성에 느슨하게 일치시킬 있습니다다음 규칙이 적용됩니다.

  • 토큰은 어떤 순서로든 일치시킬 수 있습니다.
  • 마지막 대상 속성 이름에는 모든 토큰이 일치해야 합니다.
  • 마지막 소스 속성 이름에는 하나 이상의 토큰이 일치 해야 합니다.

느슨한 일치 전략은 속성 계층 구조가 매우 다른 소스 대상 개체 모델에 사용하기에 이상적입니다 높은 수준의 모호한 일치가 감지될 있지만 알려진 개체 모델의 경우 매핑 정의에 대한 빠른 대안이 있습니다 .

 

DEFAULT

엄격한 일치 전략을 사용하면 원본 속성을 대상 속성과 엄격하게 일치시킬 있습니다 전략은 완전한 일치 정확도를 허용하여 불일치나 모호성이 발생하지 않도록 합니다그러나 소스 측과 대상 측의 속성 이름 토큰이 서로 정확하게 일치해야 합니다다음 규칙이 적용됩니다.

  • 토큰은 엄격한 순서로 일치합니다.
  • 모든 대상 속성 이름 토큰이 일치해야 합니다.
  • 모든 소스 속성 이름에는 모든 토큰이 일치해야 합니다.

엄격한 일치 전략은 TypeMap. 단점은 엄격함으로 인해 일부 대상 속성이 일치하지 않는 상태로 남을 있다는 것입니다.

 

공식문서에 따르면 표준 일치 전략은 기본적으로 구성되어 있으며 정확하지는 않지만 대부분의 시나리오에서 사용하기에 이상적이라고 표현하고 있고 default 전략이 STANDARD 것과 연관성이 있어보인다.


3. 사용법

공식 문서를 참고한 간단한 예제 코드 작성해보자 ~ 🫣

@AllArgsConstructor
@Getter
class Order {
    Customer customer;
    Address billingAddress;
}

@AllArgsConstructor
@Getter
class Customer {
    Name name;
}

@AllArgsConstructor
@Getter
class Name {
    String firstName;
    String lastName;
}

@AllArgsConstructor
@Getter
class Address {
    String street;
    String city;
}

@Setter
class OrderDTO {
    String customerFirstName;
    String customerLastName;
    String billingStreet;
    String billingCity;
}
Order order = new Order(
    new Customer(
        new Name("firstName", "lastName")
    ),
    new Address("street", "city")
);
ModelMapper modelMapper = new ModelMapper();
OrderDto orderDTO = modelMapper.map(order, OrderDto.class);

위의 간단한 사용법 이외에도 

소스 모델과 대상 모델의 필드명이 다를 경우 tokenizer 설정을 할 수 있다.

modelMapper.getConfiguration()
    .setSourceNameTokenizer(NameTokenizers.CAMEL_CASE)
    .setDestinationNameTokenizer(NameTokenizers.UNDERSCORE);

또한 필드 간 매핑 설정을 직접 해주거나 특정 필드를 SKIP 하거나 NULL 필드를 SKIP 하는 설정 또한 가능하다.

modelMapper.getConfiguration()
    .setSkipNullEnabled(true);

4. Validation

또한 공식 문서에 따르면 "개체가 예상대로 매핑되고 있는지 확인하기 위한 테스트 작성 외에도 ModelMapper 에는 대상 속성이 일치하지 않는 경우 이를 알려주는 유효성 검사가 내장되어 있습니다."라고 명시하고 있다.

 

ModelMapper는 매칭 전략에 맞지 않는 field의 값을 null값으로 초기화하기 때문에 개발자의 입장에서 속성값이 예상한 대로 매핑이 되었는지 유효성 체크를 할 수 있고 원본 속성과 일치하지 않는 대상 속성이 포함된 경우 ValidationException을 throw 하게 된다.

ModelMapper modelMapper = new ModelMapper();
OrderDto orderDTO = modelMapper.map(order, OrderDto.class);
try {
    modelMapper.validate();
} catch (ValidationException e) {
    /* Exception Handling */
}

5. ModelMapper vs MapStruct

둘 다 사용하는 입장에서 간단하게 비교해 보자면 사용에 있어서는 ModelMapper가 훨씬 간단하다고 볼 수 있다.

다만 ModelMapper는 Object mapping 과정에 있어서 리플렉션이 발생하는데 그 과정에서 MapStruct에 비해 성능적으로 불리하게 된다. 

* Reflection

리플렉션은 구체적인 클래스 타입을 알지 못하더라도 해당 클래스의 메서드, 타입, 변수들에 접근할 수 있도록 하는 Java API를 말하며 컴파일 시점이 아닌 실행 시점에 동적으로 특정 클래스의 정보를 추출할 수 있는 프로그래밍 기법

 

하지만 Java 버전이 올라가면서 리플렉션의 성능도 많이 개선되었다고 하니 ModelMapper의 사용은 성능상으로도 많이 불리해 보이진 않는다.

테스트를 해봤을 땐 MapStruct에 비해 성능상 불리하긴 하나 MapStruct는 매핑되는 Object들에 대해 converter를 작성해줘야 하니 그만큼 코드가 많이 생성되는 점을 생각해 봤을 땐 ModelMapper의 성능을 가져가면서 코드를 줄이는 것도 좋은 방법이 아닐까 싶다.

개인적으로는 성능상으로 이슈가 생겼을 때 MapStruct를 고려해 보는 방안이 어떨까 싶다.

'프로그래밍 > Java' 카테고리의 다른 글

[Java] StringTokenizer에 대해 알아보자~  (0) 2022.08.09
String vs (StringBuffer vs StringBuilder)  (0) 2022.06.07
Java에서 문자열 앞뒤 공백 제거  (0) 2022.05.31
Java로 Client IP 찾는 방법  (0) 2022.05.23
++i vs i++  (0) 2022.05.18

댓글