Zuordnen von Sammlungen mit MapStruct

1. Übersicht

In diesem Tutorial sehen wir uns an, wie Sammlungen von Objekten mit MapStruct zugeordnet werden.

Da dieser Artikel bereits ein grundlegendes Verständnis von MapStruct voraussetzt, sollten Anfänger zuerst unsere Kurzanleitung zu MapStruct lesen.

2. Sammlungen zuordnen

Im Allgemeinen funktioniert das Zuordnen von Sammlungen mit MapStruct genauso wie für einfache Typen .

Grundsätzlich müssen wir eine einfache Schnittstelle oder abstrakte Klasse erstellen und die Mapping-Methoden deklarieren. Basierend auf unseren Deklarationen generiert MapStruct den Mapping-Code automatisch. In der Regel durchläuft der generierte Code die Quellensammlung, konvertiert jedes Element in den Zieltyp und nimmt jedes Element in die Zielsammlung auf .

Schauen wir uns ein einfaches Beispiel an.

2.1. Zuordnungslisten

Betrachten wir in unserem Beispiel zunächst ein einfaches POJO als Mapping-Quelle für unseren Mapper:

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

Das Ziel wird ein einfaches DTO sein:

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

Als nächstes definieren wir unseren Mapper:

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

Schauen wir uns zum Schluss den Code MapStruct an, der von unserer EmployeeMapper- Oberfläche generiert wurde :

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

Es gibt eine wichtige Sache zu beachten. Insbesondere hat MapStruct für uns automatisch die Zuordnung von Employee zu EmployeeDTO generiert .

Es gibt Fälle, in denen dies nicht möglich ist. Angenommen, wir möchten unser Mitarbeitermodell dem folgenden Modell zuordnen:

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

In diesem Fall, wenn wir von einem nur die Methode Mapping deklarieren Liste der Mitarbeiter auf eine Liste von EmployeeFullNameDTO werden wir einen Fehler bei der Kompilierung oder Warnung wie angezeigt:

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".

Grundsätzlich bedeutet dies, dass MapStruct das Mapping in diesem Fall nicht automatisch für uns generieren konnte . Daher müssen wir die Zuordnung zwischen Employee und EmployeeFullNameDTO manuell definieren .

In Anbetracht dieser Punkte definieren wir es manuell:

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

Der generierte Code wird die Methode verwenden wir die Elemente der Quelle zur Karte definiert Liste in die Zielliste .

Dies gilt auch generell. Wenn wir eine Methode definiert haben, die den Quellelementtyp dem Zielelementtyp zuordnet, wird sie von MapStruct verwendet.

2.2. Zuordnungssätze und Karten

Das Zuordnen von Sets mit MapStruct funktioniert genauso wie mit Listen. Angenommen, wir möchten eine Gruppe von Employee- Instanzen einer Gruppe von EmployeeDTO- Instanzen zuordnen .

Nach wie vor brauchen wir einen Mapper:

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

Und MapStruct generiert den entsprechenden Code:

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

Gleiches gilt für Karten. Lassen Sie uns betrachten wir eine zuordnen möchten Karte auf eine Karte .

Dann können wir die gleichen Schritte wie zuvor ausführen:

@Mapper public interface EmployeeMapper { Map map(Map idEmployeeMap); }

Und MapStruct macht seinen Job:

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. Sammlungszuordnungsstrategien

Oft müssen wir Datentypen mit einer Eltern-Kind-Beziehung zuordnen. In der Regel haben wir einen Datentyp (übergeordnet), der als Feld eine Sammlung eines anderen Datentyps (untergeordnet) enthält.

In solchen Fällen bietet MapStruct die Möglichkeit , festzulegen, wie die untergeordneten Elemente zum übergeordneten Typ festgelegt oder hinzugefügt werden sollen. Insbesondere verfügt die Annotation @Mapper über ein Attribut collectionMappingStrategy , das ACCESSOR_ONLY , SETTER_PREFERRED , ADDER_PREFERRED oder TARGET_IMMUTABLE sein kann .

Alle diese Werte beziehen sich auf die Art und Weise, wie die untergeordneten Elemente festgelegt oder dem übergeordneten Typ hinzugefügt werden sollen. Der Standardwert ist ACCESSOR_ONLY. Dies bedeutet, dass nur Accessoren zum Festlegen der Sammlung von untergeordneten Elementen verwendet werden können .

Diese Option ist nützlich, wenn der Setter für das Feld Sammlung nicht verfügbar ist, wir aber einen Addierer haben. Ein anderer Fall, in dem dies nützlich ist, ist, wenn die Sammlung für den übergeordneten Typ unveränderlich ist . Normalerweise treten diese Fälle bei generierten Zieltypen auf.

3.1. ACCESSOR_ONLY Sammlungszuordnungsstrategie

Nehmen wir ein Beispiel, um besser zu verstehen, wie dies funktioniert.

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.

In diesem Fall werden einige Standardimplementierungen im generierten Code verwendet. Die Standardimplementierung für List ist beispielsweise ArrayList, wie aus unseren obigen Beispielen hervorgeht.

Die vollständige Liste der von MapStruct unterstützten Schnittstellen und die für jede Schnittstelle verwendeten Standardimplementierungen finden Sie in der Referenzdokumentation.

5. Schlussfolgerung

In diesem Artikel haben wir untersucht, wie Sammlungen mit MapStruct zugeordnet werden.

Zunächst haben wir uns angesehen, wie wir verschiedene Arten von Sammlungen zuordnen können. Dann haben wir gesehen, wie wir Eltern-Kind-Beziehungszuordnungen mithilfe von Sammlungszuordnungsstrategien anpassen können.

Unterwegs haben wir die wichtigsten Punkte und Dinge hervorgehoben, die beim Zuordnen von Sammlungen mit MapStruct zu beachten sind.

Wie üblich ist der vollständige Code auf GitHub verfügbar.