Картиране на колекции с MapStruct

1. Общ преглед

В този урок ще разгледаме как да картографираме колекции от обекти с помощта на MapStruct.

Тъй като тази статия предполага основно разбиране на MapStruct, начинаещите трябва първо да разгледат нашето кратко ръководство за MapStruct.

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

По принцип картографирането на колекции с MapStruct работи по същия начин, както при простите типове .

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

Нека да разгледаме един прост пример.

2.1. Картографски списъци

Първо, за нашия пример, нека разгледаме прост POJO като източник на картографиране за нашия картограф:

public class Employee { private String firstName; private String lastName; // constructor, getters and setters } 

Целта ще бъде просто DTO:

public class EmployeeDTO { private String firstName; private String lastName; // getters and setters }

След това нека дефинираме нашия картограф:

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

И накрая, нека разгледаме кода MapStruct, генериран от нашия интерфейс 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 генерира автоматично за нас картографирането от служител на служителDTO .

Има случаи, когато това не е възможно. Да приемем например, че искаме да съпоставим нашия модел на служител със следния модел:

public class EmployeeFullNameDTO { private String fullName; // getter and setter }

В този случай, ако просто декларираме метода на картографиране от списък на служител в списък на EmployeeFullNameDTO , ще получим грешка по време на компилация или предупреждение като:

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 и EmployeeFullNameDTO.

Като се имат предвид тези точки, нека го дефинираме ръчно:

@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 работи по същия начин, както при списъците. Да приемем например, че искаме да съпоставим набор от екземпляри на служители с набор от екземпляри на 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; } }

Същото се отнася и за картите. Нека помислим, че искаме да картографираме карта на карта .

След това можем да следваме същите стъпки както преди:

@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 има атрибут collectionMappingStrategy , който може да бъде ACCESSOR_ONLY , SETTER_PREFERRED , ADDER_PREFERRED или TARGET_IMMUTABLE .

Всички тези стойности се отнасят до начина, по който децата трябва да бъдат зададени или добавени към типа родител. Стойността по подразбиране е ACCESSOR_ONLY, което означава, че само аксесоари могат да се използват за задаване на Колекцията от деца.

Тази опция е полезна, когато настройката за полето „ Колекция “ не е налична, но имаме суматор. Друг случай, в който това е полезно, е когато колекцията е неизменна за родителския тип . Обикновено срещаме тези случаи при генерирани типове цели.

3.1. ACCESSOR_ONLY Стратегия за картографиране на колекции

Нека вземем пример, за да разберем по-добре как работи това.

For our example, let's create a Company class as our mapping source:

public class Company { private List employees; // getter and setter }

And the target for our mapping will be a simple DTO:

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); } }

Note that we have both the setter, setEmployees, and the adder, addEmployee, available. Also, for the adder, we are responsible for collection initialization.

Now, let's say we want to map a Company to a CompanyDTO. Then, as before we need a mapper:

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

Note that we reused the EmployeeMapper and the default collectionMappingStrategy.

Now, let's take a look at the code MapStruct generated:

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; } }

As can be seen, MapStruct uses the setter, setEmployees, to set the List of EmployeeDTO instances. This happens because here we use the default collectionMappingStrategy,ACCESSOR_ONLY.

Also, MapStruct found a method mapping a List to a List in EmployeeMapper and reused it.

3.2. ADDER_PREFERRED Collection Mapping Strategy

In contrast, let's consider we used ADDER_PREFERRED as collectionMappingStrategy:

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

Again, we want to reuse the EmployeeMapper. However, we need to explicitly add a method that can convert a single Employee to an EmployeeDTO first:

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

This is because MapStruct will use the adder to add EmployeeDTO instances to the target CompanyDTO instance one by one:

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; } }

In case the adder was not available, the setter would have been used.

We can find a complete description of all the collection mapping strategies in MapStruct's reference documentation.

4. Implementation Types for Target Collection

MapStruct supports collections interfaces as target types to mapping methods.

В този случай в генерирания код се използват някои реализации по подразбиране. Например, изпълнението по подразбиране за List е ArrayList, както може да се отбележи от нашите примери по-горе.

Можем да намерим пълния списък с интерфейси, които MapStruct поддържа и изпълненията по подразбиране, които използва за всеки интерфейс, в справочната документация.

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

В тази статия разгледахме как да картографираме колекции с помощта на MapStruct.

Първо, разгледахме как можем да картографираме различни видове колекции. След това видяхме как можем да персонализираме картографиращите връзки родител-дете, използвайки стратегии за картографиране на колекции.

По пътя подчертахме ключовите моменти и неща, които трябва да имате предвид, докато картографирате колекции с помощта на MapStruct.

Както обикновено, пълният код е достъпен в GitHub.