MapStruct教程-操作三种集合类型和两个关键点

开发 前端
MapStruct提供了一种选择如何将子类型设置或添加到父类型的方法。特别是,@Mapper注解有一个collectionMappingStrategy属性,可以是ACCESSOR_ONLY、SETTER_PREFERRED、ADDER_PREFERRED或TARGET_IMMUTABLE。

今天我们一起看下,如何使用MapStruct映射对象集合。

一、映射集合

通常来说,使用MapStruct映射集合的方式与映射简单类型相同。

我们需要创建一个简单的接口或抽象类,并声明映射方法。

根据我们的声明,MapStruct将自动生成映射代码。

通常,生成的代码会遍历源集合,将每个元素转换为目标类型,并将它们包含在目标集合中。

让我们看一个简单的例子。

(一)映射List

首先,我们将一个简单的POJO作为映射器的映射源:

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

目标将是一个简单的DTO:

public 
class 
EmployeeDTO 
{
    
private String firstName;
    
private String lastName;
}

接下来,我们定义映射器:

@Mapper
public 
interface 
EmployeeMapper 
{
    
List<EmployeeDTO> map(List<Employee> employees);
}

最后,让我们看看MapStruct从EmployeeMapper接口生成的代码:

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

需要注意的是,MapStruct会自动为我们生成了从Employee到EmployeeDTO的映射方法。

需要注意,自动生成的映射方法只会匹配字段名一致的情况,如果名字不一致,就不行了。

比如:

public 
class 
EmployeeFullNameDTO 
{
    
private String fullName;
}

在这种情况下,如果我们只是声明从Employee列表到EmployeeFullNameDTO列表的映射方法,最终是没有任何字段赋值的,MapStruct无法自动为我们生成映射,如果没有配置忽略逻辑(参考MapStruct教程(四)-忽略未映射属性的三种方式),编译时会抛出异常:

[WARNING] /path/to/EmployeeMapper.java:[15,31] Unmapped target property: "fullName". Mapping from Collection element "Employee employee" to "EmployeeFullNameDTO employeeFullNameDTO".

因此,我们需要手动定义Employee和EmployeeFullNameDTO之间的映射。

鉴于这些要点,让我们手动定义它:

@Mapper
public 
interface 
EmployeeFullNameMapper 
{
    
List<EmployeeFullNameDTO> map(List<Employee> employees);

    
@Mapping(target = "fullName", expression = "java(employee.getFirstName() + \" \" + employee.getLastName())")
    
EmployeeFullNameDTO map(Employee employee);
}

生成的代码将使用我们定义的方法将源列表的元素映射到目标列表:

public 
class 
EmployeeFullNameMapperImpl 
implements 
EmployeeFullNameMapper 
{

    
@Override
    
public List<EmployeeFullNameDTO> map(List<Employee> employees) 
{
        
if ( employees == null ) {
            
return 
null;
        }

        List<EmployeeFullNameDTO> list = new ArrayList<EmployeeFullNameDTO>( employees.size() );
        
for ( Employee employee : employees ) {
            list.add( map( employee ) );
        }

        
return list;
    }

    
@Override
    
public EmployeeFullNameDTO map(Employee employee) 
{
        
if ( employee == null ) {
            
return 
null;
        }

        EmployeeFullNameDTO employeeFullNameDTO = new EmployeeFullNameDTO();

        employeeFullNameDTO.setFullName( employee.getFirstName() + " " + employee.getLastName() );

        
return employeeFullNameDTO;
    }
}

(二)映射Set和Map

使用MapStruct映射Set的方式与List相同。

例如,假设我们要将一组Employee实例映射到一组EmployeeDTO实例。

和之前一样,我们需要一个映射器:

@Mapper
public 
interface 
EmployeeMapper 
{
    
Set<EmployeeDTO> map(Set<Employee> employees);
}

然后MapStruct将生成相应的代码:

@Override
public Set<EmployeeDTO> map(Set<Employee> employees) 
{
    
if ( employees == null ) {
        
return 
null;
    }

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

    
return set;
}

对于Map也是如此。

假设我们要将一个Map<String, Employee>映射到一个Map<String, EmployeeDTO>。

我们可以按照之前的相同步骤进行操作:

@Mapper
public 
interface 
EmployeeMapper 
{

    
Map<String, EmployeeDTO> map(Map<String, Employee> idEmployeeMap);
}

MapStruct编译后的结果:

@Override
public Map<String, EmployeeDTO> map(Map<String, Employee> idEmployeeMap) 
{
    
if ( idEmployeeMap == null ) {
        
return 
null;
    }

    Map<String, EmployeeDTO> map = new LinkedHashMap<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;
}

可以看到,无论是Set还是Map,MapStruct在初始化容器的时候,都会指定容器大小,优秀的工具细节方面都是很好的。

二、集合映射策略

有时候,我们还会用到有父子关系的数据类型:一个数据类型(父类型),它有一个包含另一个数据类型(子类型)的集合字段。

对于这种情况,MapStruct提供了一种选择如何将子类型设置或添加到父类型的方法。特别是,@Mapper注解有一个collectionMappingStrategy属性,可以是ACCESSOR_ONLY、SETTER_PREFERRED、ADDER_PREFERRED或TARGET_IMMUTABLE。所有这些值都指的是将子类型设置或添加到父类型的方式。

默认值是ACCESSOR_ONLY,即只能使用访问器来设置子类型的集合。

(一)ACCESSOR_ONLY策略

我们通过例子看看。首先,我们创建一个Company类作为我们的源类型:

public 
class 
Company 
{
    
private List<Employee> employees;
}

再定义一个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);
    }
}

注意,在这里我们定义了setter和adder两种方法,adder中做了属性初始化。

为什么要定义setter和adder?接下看。

如果我们需要将一个Company映射到一个CompanyDTO,定义一个映射器:

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

我们这里复用了EmployeeMapper,并定义了collectionMappingStrategy属性是ACCESSOR_ONLY(默认是ACCESSOR_ONLY,所以也可以忽略)。

现在让我们看看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;
    }
}

如上,MapStruct使用setter(setEmployees)来设置EmployeeDTO实例的列表,这是因为我们使用了ACCESSOR_ONLY方式操作集合。

而且,MapStruct使用了EmployeeMapper的将List映射到List的方法。

(二)ADDER_PREFERRED策略

如果我们使用ADDER_PREFERRED策略:

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

ADDER_PREFERRED策略是一个一个的追加,我们在EmployeeMapper中创建单个Employee转换为EmployeeDTO的方法:

EmployeeDTO map(Employee employee);

当然,也可以不用增加单个对象映射的方法,因为MapStruct会自动生成,不过,为了系统边界清晰,还是单独定义比较好。

生成的映射器代码是:

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

如果CompanyDTO中没有adder方法,也会再使用setter方法。

三、目标集合的实现类型

MapStruct支持将集合接口作为映射方法的目标类型。

在这种情况下,生成的代码中使用了一些默认实现。如下是对应关系:

接口类型

实现类型

Iterable

ArrayList

Collection

ArrayList

List

ArrayList

Set

LinkedHashSet

SortedSet

TreeSet

NavigableSet

TreeSet

Map

LinkedHashMap

SortedMap

TreeMap

NavigableMap

TreeMap

ConcurrentMap

ConcurrentHashMap

ConcurrentNavigableMap

ConcurrentSkipListMap

责任编辑:武晓燕 来源: 看山的小屋
相关推荐

2024-12-03 10:38:15

MapStruct继承关系Java

2011-01-18 15:35:59

jQueryJavaScriptweb

2016-10-25 13:58:36

数据图表化大数据

2022-06-20 08:50:16

TypeScript类型语法

2024-12-02 10:25:56

MapStruct枚举映射

2010-06-08 09:39:40

UML图

2023-10-13 00:00:00

Redis模块空间对象

2010-09-09 10:43:56

VPN服务

2024-11-29 11:39:30

MapStruct映射

2009-08-06 15:26:18

C#异常类型

2024-05-30 08:03:17

2009-11-06 10:54:19

WCF服务方式

2024-06-04 17:02:38

newC#编程语言

2021-04-22 09:00:48

工业物联网IIOT物联网

2010-05-11 14:08:50

MySQL数字类型

2009-06-29 18:21:29

Hibernate

2009-12-10 15:46:22

动态路由协议

2013-01-18 09:41:24

PaaS云存储红帽Linux

2012-08-10 14:38:13

2020-11-11 21:27:55

缓冲文件调用
点赞
收藏

51CTO技术栈公众号