스프링을 이용해 쇼핑몰을 만들고 있는데 ModelMapper 라이브러리를 사용하게 되어서 정리해본다.
ModelMapper는 어떤 객체에 있는 필드값을 원하는 객체에 자동으로 매핑시켜주는 라이브러리이다.
주로 DTO와 같은 클래스로 데이터를 받은 후 원하는 클래스에 넣어줄 때, Getter/Setter를 이용해 필드를 복사/붙여넣기 하는 작업을 거친다. 이때 매핑해야할 필드가 다른 경우도 많다. 다른 모델의 객체를 매핑해줘야 하는 작업이 발생할 수 있는데 이런 번거로움을 해결하기 위해 사용되는 라이브러리이다.
implementation 'org.modelmapper:modelmapper:{version}'
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>{version}</version>
</dependency>
@Getter @Setter
class Order{
Customer customer;
Address billingAddress;
}
@Getter @Setter
class Customer{
Name name;
}
@Getter @Setter
class Name{
String firstName;
String lastName;
}
@Getter @Setter
class Address{
String street;
String city;
}
class OrderDto{
String customerFirstName;
String customerLastName;
String billingStreet;
String billingCity;
이와 같은 클래스가 정의되었다고 가정하자. 우리는 ModelMapper.map(Object source, Class<D> destinationType)를 사용해 order 객체를 orderDto에 매핑할 수 있다.
Order order = new Order(
new Customer(new Name("LastName", "FirstName")),
new Address("Street", "City");
);
ModelMapper mapper = new ModelMapper();
// 매핑하기
OrderDto orderDto = mapper.map(order, OrderDto.class);

이처럼 map(source, destination) 메서드가 호출되면 source와 destination 타입을 분석해 매칭 전략이나 기타 설정값들에 따라 일치하는 속성을 결정하게 된다. 하지만 속성간의 매핑을 명시적으로 정의해야 하는 경우도 존재한다. 이런 경우에는 TypeMap<> 을 사용한다.
class Item{
private String name;
private Integer stock;
private Integer price;
private Boolean sale;
}
class Bill{
private String itemName;
private Integer qty;
private Integer singlePrice;
private Double discount;
}
우리는 Item과 Bill을 매핑시킬 것이다
mapper.typeMap(Item.class, Bill.class).addMappinig(m -> {
m.map(Item::getStock, Bill::setQty);
m.map(Item::getPrice, Bill::setSinglePrice);
});
Bill bill2 = mapper.map(item, Bill.class);
이렇게되면 stock과 price는 각각 잘 매핑된 것을 확인할 수 있지만 sale -> discount 매핑은 타입이 다르기 때문에 Converter 인터페이스를 사용해야한다.
우리는 이때 mapper.using(Converter<S, D>)을 사용해 타입을 변환해줄 것이다.
mapper.typeMap(Item.class, Bill.class).addMappings(m -> {
m.map(Item::getStock, Bill::setQty);
m.map(Item::getPrice, Bill::setSinglePrice);
m.using((Converter<Boolean, Double>) ctx -> ctx.getSource() ? 30.0 : 0.0)
.map(Item::isSale, Bill::setDiscount);
});
Bill bill2 = mapper.map(item, Bill.class);
이렇게되면 sale 까지 모두 정상적으로 매핑된 것을 확인할 수 있다.