文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2024-11-28 13:26

关注

一、映射集合

通常来说,使用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 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会自动为我们生成了从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 map(List employees);

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

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

public 
class 
EmployeeFullNameMapperImpl 
implements 
EmployeeFullNameMapper 
{

    
@Override
    
public List map(List employees) 
{
        
if ( employees == null ) {
            
return 
null;
        }

        List list = new ArrayList( 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 map(Set employees);
}

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

@Override
public Set map(Set employees) 
{
    
if ( employees == null ) {
        
return 
null;
    }

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

    
return set;
}

对于Map也是如此。

假设我们要将一个Map映射到一个Map

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

@Mapper
public 
interface 
EmployeeMapper 
{

    
Map map(Map idEmployeeMap);
}

MapStruct编译后的结果:

@Override
public Map map(Map idEmployeeMap) 
{
    
if ( idEmployeeMap == null ) {
        
return 
null;
    }

    Map map = new LinkedHashMap( 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;
}

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

二、集合映射策略

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

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

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

(一)ACCESSOR_ONLY策略

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

public 
class 
Company 
{
    
private List employees;
}

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

注意,在这里我们定义了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

来源:看山的小屋内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯