1. Overview
1.概述
In this tutorial, we’ll learn how to map collections of objects using MapStruct.
在本教程中,我们将学习如何使用MapStruct来映射对象的集合。
Since this article already assumes a basic understanding of MapStruct, beginners should check out our quick guide to MapStruct first.
由于本文已经假设了对MapStruct的基本了解,初学者应该先看看我们的MapStruct快速指南。
2. Mapping Collections
2.绘图收集
In general, mapping collections with MapStruct works the same way as for simple types.
一般来说,用MapStruct映射集合的方法与简单类型的方法相同。
Basically, we have to create a simple interface or abstract class, and declare the mapping methods. Based on our declarations, MapStruct will generate the mapping code automatically. Typically, the generated code will loop over the source collection, convert each element to the target type, and include each of them in the target collection.
基本上,我们必须创建一个简单的接口或抽象类,并声明映射方法。基于我们的声明,MapStruct会自动生成映射代码。通常情况下,生成的代码会在源集合上循环,将每个元素转换为目标类型,并将每个元素纳入目标集合。
Let’s take a look at a simple example.
让我们看一下一个简单的例子。
2.1. Mapping Lists
2.1.映射列表
First, we’ll consider a simple POJO as the mapping source for our mapper:
首先,我们将考虑一个简单的POJO作为我们映射器的映射源。
public class Employee {
private String firstName;
private String lastName;
// constructor, getters and setters
}
The target will be a simple DTO:
目标将是一个简单的DTO。
public class EmployeeDTO {
private String firstName;
private String lastName;
// getters and setters
}
Next, we’ll define our mapper:
接下来,我们将定义我们的映射器。
@Mapper
public interface EmployeeMapper {
List<EmployeeDTO> map(List<Employee> employees);
}
Finally, let’s look at the code MapStruct generated from our EmployeeMapper interface:
最后,让我们看看从我们的EmployeeMapper接口生成的代码MapStruct。
public class EmployeeMapperImpl implements EmployeeMapper {
@Override
public List<EmployeeDTO> map(List<Employee> employees) {
if (employees == null) {
return null;
}
List<EmployeeDTO> list = new ArrayList<EmployeeDTO>(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;
}
}
One important thing to note is that MapStruct automatically generated the mapping from Employee to EmployeeDTO for us.
需要注意的一件事是,MapStruct自动为我们生成了从Employee到EmployeeDTO的映射。
There are cases when this isn’t possible. For example, let’s say we want to map our Employee model to the following model:
有些情况下这是不可能的。例如,假设我们想把我们的Employee模型映射到以下模型。
public class EmployeeFullNameDTO {
private String fullName;
// getter and setter
}
In this case, if we just declare the mapping method from a List of Employee to a List of EmployeeFullNameDTO, we’ll receive a compile-time error or warning:
在这种情况下,如果我们只是声明从List的Employee到List的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".
Basically, this means that, in this case, MapStruct couldn’t generate the mapping automatically for us. Therefore, we need to manually define the mapping between Employee and EmployeeFullNameDTO.
基本上,这意味着,在这种情况下,MapStruct无法为我们自动生成映射。因此,我们需要手动定义Employee和EmployeeFullNameDTO的映射。
Given these points, let’s manually define it:
鉴于这些要点,让我们手动定义一下。
@Mapper
public interface EmployeeFullNameMapper {
List<EmployeeFullNameDTO> map(List<Employee> employees);
default EmployeeFullNameDTO map(Employee employee) {
EmployeeFullNameDTO employeeInfoDTO = new EmployeeFullNameDTO();
employeeInfoDTO.setFullName(employee.getFirstName() + " " + employee.getLastName());
return employeeInfoDTO;
}
}
The generated code will use the method we defined to map the elements of the source List to the target List.
生成的代码将使用我们定义的方法,将源List的元素映射到目标List。
This also applies in general. If we’ve defined a method that maps the source element type to the target element type, MapStruct will use it.
这也适用于一般情况。如果我们定义了一个将源元素类型映射到目标元素类型的方法,MapStruct将使用它。
2.2. Mapping Sets and Maps
2.2.映射集和地图
Mapping sets with MapStruct works in the same way as with lists. For example, let’s say we want to map a Set of Employee instances to a Set of EmployeeDTO instances.
使用 MapStruct 映射集合的方式与使用列表的方式相同。例如,我们想把一个Set of Employee instances映射到一个EmployeeDTO instances的Set。
As before, we need a mapper:
和以前一样,我们需要一个映射器。
@Mapper
public interface EmployeeMapper {
Set<EmployeeDTO> map(Set<Employee> employees);
}
Then MapStruct will generate the appropriate code:
然后MapStruct会生成相应的代码。
public class EmployeeMapperImpl implements EmployeeMapper {
@Override
public Set<EmployeeDTO> map(Set<Employee> employees) {
if (employees == null) {
return null;
}
Set<EmployeeDTO> set =
new HashSet<EmployeeDTO>(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;
}
}
The same applies to maps. Let’s suppose we want to map a Map<String, Employee> to a Map<String, EmployeeDTO>.
这同样适用于地图。假设我们想把一个Map<String, Employee> 映射到一个Map<String, EmployeeDTO> 。
We can follow the same steps as before:
我们可以按照之前的步骤进行。
@Mapper
public interface EmployeeMapper {
Map<String, EmployeeDTO> map(Map<String, Employee> idEmployeeMap);
}
And MapStruct does its job:
而MapStruct则完成了它的工作。
public class EmployeeMapperImpl implements EmployeeMapper {
@Override
public Map<String, EmployeeDTO> map(Map<String, Employee> idEmployeeMap) {
if (idEmployeeMap == null) {
return null;
}
Map<String, EmployeeDTO> map = new HashMap<String, EmployeeDTO>(Math.max((int)(idEmployeeMap.size() / .75f) + 1, 16));
for (java.util.Map.Entry<String, Employee> 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. Collections Mapping Strategies
3.馆藏图谱策略
Often, we need to map data types having a parent-child relationship. Typically, we have a data type (parent) that has as a field a Collection of another data type (child).
通常,我们需要映射具有父子关系的数据类型。通常,我们有一个数据类型(父),其字段是另一个数据类型(子)的Collection。
For such cases, MapStruct offers a way to choose how to set or add the children to the parent type. In particular, the @Mapper annotation has a collectionMappingStrategy attribute that can be ACCESSOR_ONLY, SETTER_PREFERRED, ADDER_PREFERRED or TARGET_IMMUTABLE.
对于这种情况,MapStruct提供了一种方法来选择如何设置或添加子类型到父类型。特别是,@Mapper 注解有一个collectionMappingStrategy 属性,可以是ACCESSOR_ONLY、SETTER_PREFERRED、ADDER_PREFERRED或TARGET_IMMUTABLE。
All of these values refer to the way the children should be set or added to the parent type. The default value is ACCESSOR_ONLY, which means that only accessors can be used to set the Collection of children.
所有这些值都是指子类型应该被设置或添加到父类型中的方式。默认值是ACCESSOR_ONLY,这意味着只能使用访问器来设置子类型的Collection。
This option comes in handy when the setter for the Collection field isn’t available, but we have an adder. Another case where this is useful is when the Collection is immutable on the parent type. Usually, we encounter these cases in generated target types.
当Collection字段的setter不可用,但我们有一个adder时,这个选项就很有用了。另一种情况是当Collection在父类型上是不可更改的时候。通常情况下,我们在生成的目标类型中会遇到这些情况。
3.1. ACCESSOR_ONLY Collection Mapping Strategy
3.1.ACCESSOR_ONLY集合映射策略
Let’s look at an example to better understand how this works.
让我们看一个例子,以更好地理解这一点的作用。
We’ll create a Company class as our mapping source:
我们将创建一个Company类作为我们的映射源。
public class Company {
private List<Employee> employees;
// getter and setter
}
The target for our mapping will be a simple DTO:
我们映射的目标将是一个简单的DTO。
public class CompanyDTO {
private List<EmployeeDTO> employees;
public List<EmployeeDTO> getEmployees() {
return employees;
}
public void setEmployees(List<EmployeeDTO> 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.
请注意,我们有setter,setEmployees,和adder,addEmployee,可用。另外,对于添加器,我们负责集合的初始化。
Now, let’s say we want to map a Company to a CompanyDTO. Then, as before, we need a mapper:
现在,假设我们想把一个Company映射到一个CompanyDTO。那么,和以前一样,我们需要一个映射器。
@Mapper(uses = EmployeeMapper.class)
public interface CompanyMapper {
CompanyDTO map(Company company);
}
Note that we reused the EmployeeMapper and the default collectionMappingStrategy.
注意,我们重新使用了EmployeeMapper和默认的collectionMappingStrategy.。
Now let’s take a look at the code MapStruct generated:
现在让我们来看看MapStruct生成的代码。
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 we can see, MapStruct uses the setter, setEmployees, to set the List of EmployeeDTO instances. This happens because we used the default collectionMappingStrategy, ACCESSOR_ONLY.
我们可以看到,MapStruct使用setter,setEmployees,来设置EmployeeDTO实例的List。这是因为我们使用了默认的collectionMappingStrategy, ACCESSOR_ONLY.。
MapStruct also found a method mapping a List<Employee> to a List<EmployeeDTO> in EmployeeMapper and reused it.
MapStruct还发现了一个将List<Employee> 映射到List<EmployeeDTO> 中的EmployeeMapper的方法,并重用了它。
3.2. ADDER_PREFERRED Collection Mapping Strategy
3.2.ADDER_PREFERRED集合映射策略
In contrast, let’s suppose we used ADDER_PREFERRED as the collectionMappingStrategy:
相反,让我们假设我们使用ADDER_PREFERRED作为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:
同样,我们想重用EmployeeMapper。然而,我们需要明确地添加一个方法,可以将单个Employee转换为EmployeeDTO,首先。
@Mapper
public interface EmployeeMapper {
EmployeeDTO map(Employee employee);
List map(List employees);
Set map(Set employees);
Map<String, EmployeeDTO> map(Map<String, Employee> idEmployeeMap);
}
This is because MapStruct will use the adder to add EmployeeDTO instances to the target CompanyDTO instance one by one:
这是因为MapStruct将使用加法器将EmployeeDTO实例逐一添加到目标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;
}
}
If the adder is unavailable, the setter will be used.
如果加法器不可用,将使用设定器。
We can find a complete description of all the collection mapping strategies in MapStruct’s reference documentation.
我们可以在MapStruct的参考文档中找到所有集合映射策略的完整描述。
4. Implementation Types for Target Collection
4.目标收集的实施类型
MapStruct supports collections interfaces as target types to mapping methods.
MapStruct支持集合接口作为映射方法的目标类型。
In this case, some default implementations are used in the generated code. For example, the default implementation for List is ArrayList, as can be noted from our examples above.
在这种情况下,一些默认的实现被用于生成的代码中。例如,List的默认实现是ArrayList,,从我们上面的例子可以看出。
We can find the complete list of interfaces MapStruct supports, and the default implementations it uses for each interface, in the reference documentation.
我们可以在参考文档中找到MapStruct支持的接口的完整列表,以及它对每个接口使用的默认实现。
5. Conclusion
5.总结
In this article, we explored how to map collections using MapStruct.
在这篇文章中,我们探讨了如何使用MapStruct来映射集合。
First, we looked at how to map different types of collections. Then, we learned how to customize parent-child relationship mappers using collection mapping strategies.
首先,我们研究了如何映射不同类型的集合。然后,我们学习了如何使用集合映射策略来定制父子关系映射器。
Along the way, we highlighted the key points and things to keep in mind while mapping collections using MapStruct.
一路上,我们强调了使用MapStruct绘制集合时需要注意的关键点和事项。
As usual, the complete code is available over on GitHub.
像往常一样,完整的代码可以在GitHub上找到,。