«1. Обзор

В этом руководстве мы увидим, как использовать несколько исходных объектов с MapStruct.

2. Объект с одним исходным кодом

Наиболее распространенный вариант использования MapStruct — сопоставление одного объекта с другим. Предположим, у нас есть класс Customer:

class Customer {

    private String firstName;
    private String lastName;

    // getters and setters

}

Далее предположим, что существует соответствующий класс CustomerDto:

class CustomerDto {

    private String forename;
    private String surname;

    // getters and setters

}

Теперь мы можем определить преобразователь, который сопоставляет объект Customer с объектом CustomerDto:

@Mapper
public interface CustomerDtoMapper {

    @Mapping(source = "firstName", target = "forename")
    @Mapping(source = "lastName", target = "surname")
    CustomerDto from(Customer customer);

}

~~ ~ 3. Несколько исходных объектов

Иногда нам нужно, чтобы целевой объект имел свойства из нескольких исходных объектов. Давайте представим, что мы пишем приложение для покупок.

Нам нужно построить адрес доставки для отправки наших товаров:

class DeliveryAddress {

    private String forename;
    private String surname;
    private String street;
    private String postalcode;
    private String county;

    // getters and setters

}

У каждого клиента может быть несколько адресов. Один из них может быть домашним адресом. Другой может быть рабочим адресом:

class Address {

    private String street;
    private String postalcode;
    private String county;

    // getters and setters

}

Теперь нам нужен маппер, который создает адрес доставки из клиента и одного из его адресов. MapStruct поддерживает это, имея несколько исходных объектов:

@Mapper
interface DeliveryAddressMapper {

    @Mapping(source = "customer.firstName", target = "forename")
    @Mapping(source = "customer.lastName", target = "surname")
    @Mapping(source = "address.street", target = "street")
    @Mapping(source = "address.postalcode", target = "postalcode")
    @Mapping(source = "address.county", target = "county")
    DeliveryAddress from(Customer customer, Address address);

}

Давайте посмотрим на это в действии, написав небольшой тест:

// given a customer
Customer customer = new Customer().setFirstName("Max")
  .setLastName("Powers");

// and some address
Address homeAddress = new Address().setStreet("123 Some Street")
  .setCounty("Nevada")
  .setPostalcode("89123");

// when calling DeliveryAddressMapper::from
DeliveryAddress deliveryAddress = deliveryAddressMapper.from(customer, homeAddress);

// then a new DeliveryAddress is created, based on the given customer and his home address
assertEquals(deliveryAddress.getForename(), customer.getFirstName());
assertEquals(deliveryAddress.getSurname(), customer.getLastName());
assertEquals(deliveryAddress.getStreet(), homeAddress.getStreet());
assertEquals(deliveryAddress.getCounty(), homeAddress.getCounty());
assertEquals(deliveryAddress.getPostalcode(), homeAddress.getPostalcode());

Когда у нас есть более одного параметра, мы можем адресовать их с помощью точечной записи внутри @Mapping аннотация. Например, чтобы обратиться к свойству firstName параметра с именем customer, мы просто пишем «customer.firstName».

Однако мы не ограничиваемся двумя исходными объектами. Подойдет любое число.

4. Обновление существующих объектов с помощью @MappingTarget

До сих пор у нас были преобразователи, создающие новые экземпляры целевого класса. Имея несколько исходных объектов, теперь мы также можем предоставить экземпляр для обновления.

Например, предположим, что мы хотим обновить связанные с клиентом свойства адреса доставки. Все, что нам нужно, это иметь один из параметров того же типа, что и возвращаемый методом, и аннотировать его с помощью @MappingTarget:

@Mapper
interface DeliveryAddressMapper {

    @Mapping(source = "address.postalcode", target = "postalcode")
    @Mapping(source = "address.county", target = "county")
    DeliveryAddress updateAddress(@MappingTarget DeliveryAddress deliveryAddress, Address address);

}

Итак, давайте продолжим и проведем быстрый тест с экземпляром DeliveryAddress: ~~ ~

// given a delivery address
DeliveryAddress deliveryAddress = new DeliveryAddress().setForename("Max")
  .setSurname("Powers")
  .setStreet("123 Some Street")
  .setCounty("Nevada")
  .setPostalcode("89123");

// and some new address
Address newAddress = new Address().setStreet("456 Some other street")
  .setCounty("Arizona")
  .setPostalcode("12345");

// when calling DeliveryAddressMapper::updateAddress
DeliveryAddress updatedDeliveryAddress = deliveryAddressMapper.updateAddress(deliveryAddress, newAddress);

// then the *existing* delivery address is updated
assertSame(deliveryAddress, updatedDeliveryAddress);

assertEquals(deliveryAddress.getStreet(), newAddress.getStreet());
assertEquals(deliveryAddress.getCounty(), newAddress.getCounty());
assertEquals(deliveryAddress.getPostalcode(), newAddress.getPostalcode());

5. Заключение

MapStruct позволяет нам передавать более одного исходного параметра в методы отображения. Например, это удобно, когда мы хотим объединить несколько объектов в один.

Другой вариант использования — сам целевой объект является одним из исходных параметров. Используя аннотацию @MappingTarget, данный объект можно обновить на месте.

Обязательно ознакомьтесь со всеми этими примерами на GitHub.