Рубрики
Без рубрики

Картографические коллекции со структурой карты

Узнайте, как сопоставлять коллекции с помощью структуры карт.

Автор оригинала: baeldung.

1. Обзор

В этом уроке мы рассмотрим, как сопоставлять коллекции объектов с помощью Map Struct.

Поскольку эта статья предполагает уже базовое понимание MapStruct, новичкам следует сначала ознакомиться с нашим кратким руководством по MapStruct.

2. Картографические коллекции

В общем случае сопоставление коллекций с MapStruct работает так же, как и для простых типов .

В принципе, мы должны создать простой интерфейс или абстрактный класс и объявить методы отображения. Основываясь на наших объявлениях, MapStruct автоматически сгенерирует код сопоставления. Как правило, сгенерированный код будет перебирать исходную коллекцию, преобразовывать каждый элемент в целевой тип и включать каждый из них в целевую коллекцию .

Давайте рассмотрим простой пример.

2.1. Сопоставление списков

Во-первых, для нашего примера давайте рассмотрим простой POJO в качестве источника отображения для нашего картографа:

public class Employee {
    private String firstName;
    private String lastName;

    // constructor, getters and setters
}

Целью будет простое:

public class EmployeeDTO {

    private String firstName;
    private String lastName;

    // getters and setters
}

Далее, давайте определим нашего картографа:

@Mapper
public interface EmployeeMapper {
    List map(List employees);
}

Наконец, давайте рассмотрим структуру кода, сгенерированную из нашего интерфейса EmployeeMapper :

public class EmployeeMapperImpl implements EmployeeMapper {

    @Override
    public List map(List employees) {
        if (employees == null) {
            return null;
        }

        List list = new ArrayList(employees.size());
        for (Employee employee : employees) {
            list.add(employeeToEmployeeDTO(employee));
        }

        return list;
    }

    protected EmployeeDTO employeeToEmployeeDTO(Employee employee) {
        if (employee == null) {
            return null;
        }

        EmployeeDTO employeeDTO = new EmployeeDTO();

        employeeDTO.setFirstName(employee.getFirstName());
        employeeDTO.setLastName(employee.getLastName());

        return employeeDTO;
    }
}

Следует отметить одну важную вещь. В частности, MapStruct автоматически генерирует для нас отображение из Employee в Employee В .

Бывают случаи, когда это невозможно. Например, предположим, что мы хотим сопоставить нашу модель Employee со следующей моделью:

public class EmployeeFullNameDTO {

    private String fullName;

    // getter and setter
}

В этом случае, если мы просто объявим метод сопоставления из List of Employee в List of Employee FullName D В , мы получим ошибку во время компиляции или предупреждение, например:

Warning:(11, 31) java: Unmapped target property: "fullName". 
  Mapping from Collection element "com.baeldung.mapstruct.mappingCollections.model.Employee employee" to 
  "com.baeldung.mapstruct.mappingCollections.dto.EmployeeFullNameDTO employeeFullNameDTO".

В принципе, это означает, что MapStruct не может автоматически генерировать отображение для нас в данном случае . Поэтому нам нужно вручную определить сопоставление между Employee и Employee FullName D TO.

Учитывая эти моменты, давайте определим его вручную:

@Mapper
public interface EmployeeFullNameMapper {

    List map(List employees);

    default EmployeeFullNameDTO map(Employee employee) {
        EmployeeFullNameDTO employeeInfoDTO = new EmployeeFullNameDTO();
        employeeInfoDTO.setFullName(employee.getFirstName() + " " + employee.getLastName());

        return employeeInfoDTO;
    }
}

Сгенерированный код будет использовать определенный нами метод для сопоставления элементов исходного Списка с целевым Списком .

Это касается и в целом. Если мы определили метод, который сопоставляет тип исходного элемента с типом целевого элемента, MapStruct будет использовать его.

2.2. Картографические наборы и карты

Наборы сопоставлений с MapStruct работают так же, как и со списками. Например, предположим, что мы хотим сопоставить Набор экземпляров Employee с Набором экземпляров EmployeeDTO экземплярами|/.

Как и прежде, нам нужен картограф:

@Mapper
public interface EmployeeMapper {

    Set map(Set employees);
}

И MapStruct сгенерирует соответствующий код:

public class EmployeeMapperImpl implements EmployeeMapper {

    @Override
    public Set map(Set employees) {
        if (employees == null) {
            return null;
        }

        Set set = 
          new HashSet(Math.max((int)(employees.size() / .75f ) + 1, 16));
        for (Employee employee : employees) {
            set.add(employeeToEmployeeDTO(employee));
        }

        return set;
    }

    protected EmployeeDTO employeeToEmployeeDTO(Employee employee) {
        if (employee == null) {
            return null;
        }

        EmployeeDTO employeeDTO = new EmployeeDTO();

        employeeDTO.setFirstName(employee.getFirstName());
        employeeDTO.setLastName(employee.getLastName());

        return employeeDTO;
    }
}

То же самое относится и к картам. Давайте рассмотрим, что мы хотим сопоставить Map Employee> с Map Employee TO> . Employee>

Затем мы можем выполнить те же шаги, что и раньше:

@Mapper
public interface EmployeeMapper {

    Map map(Map idEmployeeMap);
}

И MapStruct выполняет свою работу:

public class EmployeeMapperImpl implements EmployeeMapper {

    @Override
    public Map map(Map idEmployeeMap) {
        if (idEmployeeMap == null) {
            return null;
        }

        Map map = new HashMap(Math.max((int)(idEmployeeMap.size() / .75f) + 1, 16));

        for (java.util.Map.Entry entry : idEmployeeMap.entrySet()) {
            String key = entry.getKey();
            EmployeeDTO value = employeeToEmployeeDTO(entry.getValue());
            map.put(key, value);
        }

        return map;
    }

    protected EmployeeDTO employeeToEmployeeDTO(Employee employee) {
        if (employee == null) {
            return null;
        }

        EmployeeDTO employeeDTO = new EmployeeDTO();

        employeeDTO.setFirstName(employee.getFirstName());
        employeeDTO.setLastName(employee.getLastName());

        return employeeDTO;
    }
}

3. Стратегии сопоставления коллекций

Часто нам нужно сопоставлять типы данных, имеющие отношения “родитель-потомок”. Как правило, у нас есть тип данных (родительский), имеющий в качестве поля Коллекцию другого типа данных (дочерний).

В таких случаях MapStruct предлагает способ выбора способа установки или добавления дочерних элементов в родительский тип. В частности, в аннотации @Mapper есть стратегия сопоставления коллекции атрибут, который может быть ACCESSED_ONLY , SETTER_PREFERRED , ADDER_PREFERRED или TARGET_IMMUTABLE .

Все эти значения относятся к способу установки или добавления дочерних элементов в родительский тип. Значение по умолчанию равно ACCESSORY_ONLY, что означает, что для установки Коллекции дочерних элементов можно использовать только методы доступа.

Эта опция удобна, когда сеттер для поля Collection недоступен, но у нас есть сумматор. Другой случай, в котором это полезно, – это , когда Коллекция неизменна для родительского типа . Обычно мы сталкиваемся с такими случаями в сгенерированных целевых типах.

3.1. Стратегия сопоставления коллекций ACCESSOR_ONLY

Давайте возьмем пример, чтобы лучше понять, как это работает.

Для нашего примера давайте создадим класс Company в качестве источника отображения:

public class Company {

    private List employees;

   // getter and setter
}

И целью для нашего картографирования будет простое:

public class CompanyDTO {

    private List employees;

    public List getEmployees() {
        return employees;
    }

    public void setEmployees(List employees) {
        this.employees = employees;
    }

    public void addEmployee(EmployeeDTO employeeDTO) {
        if (employees == null) {
            employees = new ArrayList<>();
        }

        employees.add(employeeDTO);
    }
}

Обратите внимание, что у нас есть как сеттер, set Employees, , так и сумматор, add Employee, . Кроме того, для сумматора мы несем ответственность за инициализацию сбора.

Теперь предположим, что мы хотим сопоставить Компанию с Компанией D. Затем, как и раньше, нам нужен картограф:

@Mapper(uses = EmployeeMapper.class)
public interface CompanyMapper {
    CompanyDTO map(Company company);
}

Обратите внимание, что мы повторно использовали EmployeeMapper и стратегию сопоставления коллекций по умолчанию .

Теперь давайте взглянем на сгенерированную карту кода:

public class CompanyMapperImpl implements CompanyMapper {

    private final EmployeeMapper employeeMapper = Mappers.getMapper(EmployeeMapper.class);

    @Override
    public CompanyDTO map(Company company) {
        if (company == null) {
            return null;
        }

        CompanyDTO companyDTO = new CompanyDTO();

        companyDTO.setEmployees(employeeMapper.map(company.getEmployees()));

        return companyDTO;
    }
}

Как видно, MapStruct использует сеттер set Employees для установки Списка Сотрудников В экземпляры . Это происходит потому, что здесь мы используем стратегию сопоставления коллекций по умолчанию , /ACCESSORY_ONLY.

Кроме того, MapStruct нашел метод сопоставления List с List в EmployeeMapper и повторно использовал его.

3.2. Стратегия сопоставления коллекций ADDER_PREFERRED

Напротив, давайте рассмотрим, что мы использовали ADDER_PREFERRED как стратегию сопоставления коллекций :

@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED,
        uses = EmployeeMapper.class)
public interface CompanyMapperAdderPreferred {
    CompanyDTO map(Company company);
}

Опять же, мы хотим повторно использовать EmployeeMapper . Однако нам нужно явно добавить метод, который может преобразовать одного Сотрудника в Сотрудника В first :

@Mapper
public interface EmployeeMapper {
    EmployeeDTO map(Employee employee);
    List map(List employees);
    Set map(Set employees);
    Map map(Map idEmployeeMap);
}

Это связано с тем, что MapStruct будет использовать сумматор для добавления Сотрудников В экземпляры в целевой CompanyDTO экземпляр один за другим :

public class CompanyMapperAdderPreferredImpl implements CompanyMapperAdderPreferred {

    private final EmployeeMapper employeeMapper = Mappers.getMapper( EmployeeMapper.class );

    @Override
    public CompanyDTO map(Company company) {
        if ( company == null ) {
            return null;
        }

        CompanyDTO companyDTO = new CompanyDTO();

        if ( company.getEmployees() != null ) {
            for ( Employee employee : company.getEmployees() ) {
                companyDTO.addEmployee( employeeMapper.map( employee ) );
            }
        }

        return companyDTO;
    }
}

В случае, если бы сумматор был недоступен, был бы использован сеттер.

Мы можем найти полное описание всех стратегий сопоставления коллекций в Mapstruct справочная документация .

4. Типы реализации для целевого сбора

MapStruct поддерживает интерфейсы коллекций в качестве целевых типов для методов сопоставления.

В этом случае в сгенерированном коде используются некоторые реализации по умолчанию. Например, реализация по умолчанию для List – это ArrayList , как можно отметить из приведенных выше примеров.

Полный список интерфейсов, поддерживаемых MapStruct, и реализаций по умолчанию, которые он использует для каждого интерфейса, мы можем найти в справочной документации .

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

В этой статье мы рассмотрели, как сопоставлять коллекции с помощью структуры карт.

Во-первых, мы рассмотрели, как мы можем сопоставлять различные типы коллекций. Затем мы увидели, как мы можем настроить картографы отношений между родителями и детьми, используя стратегии сопоставления коллекций.

По пути мы выделили ключевые моменты и вещи, которые следует иметь в виду при сопоставлении коллекций с помощью MapStruct.

Как обычно, полный код доступен на GitHub .