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 { Listmap(List employees); }
Наконец, давайте рассмотрим структуру кода, сгенерированную из нашего интерфейса EmployeeMapper :
public class EmployeeMapperImpl implements EmployeeMapper { @Override public Listmap(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 { Listmap(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 { Setmap(Set employees); }
И MapStruct сгенерирует соответствующий код:
public class EmployeeMapperImpl implements EmployeeMapper { @Override public Setmap(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 { Mapmap(Map idEmployeeMap); }
И MapStruct выполняет свою работу:
public class EmployeeMapperImpl implements EmployeeMapper { @Override public Mapmap(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 Listemployees; // getter and setter }
И целью для нашего картографирования будет простое:
public class CompanyDTO { private Listemployees; 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); Mapmap(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 .