MapStruct 一文读懂

Posted 在奋斗的大道

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MapStruct 一文读懂相关的知识,希望对你有一定的参考价值。

目录

1、MapStruct 简介

1.1 MapStruct Maven引入 

2、MapStruct 基础操作 

2.1 MapStruct 基本映射

 2.2 MapStruct 指定默认值

2.3 MapStruct 表达式

2.4 MapStruct 时间格式

2.5 MapStruct 数字格式

3、MapStruct 组合映射

3.1 多参数源映射

3.2 使用其他参数值

 3.3 嵌套映射

  3.4 逆映射

  3.4 继承映射

 3.5 共享映射

3.6 自定义方法

3.6.1 自定义类型转换方法

3.6.2 使用@Qualifier

3.6.3  使用@namd

4、MapStruct 集合映射、Map映射、枚举映射和Stream 流映射

4.1 集合映射

4.2 Map映射

4.3 枚举映射

4.4 Stream流映射

5、MapStruct 其他

5.1 MapStruct 异常处理

5.2 MapStruct 自定义映射

5.3 MapStruct Null值处理

6、MapStruct 常见注解总结


1、MapStruct 简介

MapStruct是基于JSR269规范的一个Java注解处理器,用于为Java Bean生成类型安全且高性能的映射。它基于编译阶段生成get/set代码,此实现过程中没有反射,不会造成额外的性能损失。

1.1 MapStruct Maven引入 

在pom.xml文件添加MapStruct 相关依赖

            <!--mapStruct依赖 高性能对象映射-->
            <!--mapstruct核心-->
            <dependency>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct</artifactId>
                <version>1.5.0.Beta1</version>
            </dependency>
            <!--mapstruct编译-->
            <dependency>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct-processor</artifactId>
                <version>1.5.0.Beta1</version>
            </dependency>

2、MapStruct 基础操作 

2.1 MapStruct 基本映射

创建MapStruct 映射步骤总结:

  1. 添加MapStruct jar包依赖
  2. 新增接口或抽象类,并且使用org.mapstruct.Mapper注解标签修饰。
  3. 添加自定义转换方法

示例:创建Person 和PersonDto 类定义,通过MapStruct 实现Person 类实例对象转换PersonDto l类实例对象。

package com.zzg.mapstruct.entity;

import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;

@Data
public class Person 
    String describe;

    private String id;

    private String name;

    private int age;

    private BigDecimal source;

    private double height;

    private Date createTime;

package com.zzg.mapstruct.entity;

import lombok.Data;
@Data
public class PersonDTO 
    String describe;

    private Long id;

    private String personName;

    private String age;

    private String source;

    private String height;

package com.zzg.mapstruct.mapper;

import com.zzg.mapstruct.entity.Person;
import com.zzg.mapstruct.entity.PersonDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

/**
 * mapstruct 工具类定义步骤:
 * 1、添加MapStruct jar包依赖
 * 2、新增接口或抽象类,并且使用org.mapstruct.Mapper注解标签修饰。
 * 3、添加自定义转换方法
 */
@Mapper
public interface PersonMapper 
    PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);

    PersonDTO conver(Person person);

测试:

package com.zzg.mapstruct.test;

import com.zzg.mapstruct.entity.Person;
import com.zzg.mapstruct.entity.PersonDTO;
import com.zzg.mapstruct.mapper.PersonMapper;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;

public class OneTest 
    public static void main(String[] args) throws ParseException 
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");

        Person person = Person.builder().age(31).createTime(format.parse("1991-12-20")).id("1").describe("Java 开发")
                .height(180L).name("在奋斗的大道上").source(new BigDecimal(10000)).build();

        PersonDTO personDTO = PersonMapper.INSTANCT.conver(person);
        System.out.println(personDTO);
    

执行截图:

PersonDTO(describe=Java 开发, id=1, personName=null, age=31, source=10000, height=180.0)

优化:Person 类实例对象 转换PersonDto 类实例对象发现 Person.name 属性无法与PersonDto.personName  属性无法实现一一对应,可以通过@Mappering 注解标签实现不同属性名称转换。

package com.zzg.mapstruct.mapper;

import com.zzg.mapstruct.entity.Person;
import com.zzg.mapstruct.entity.PersonDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

/**
 * mapstruct 工具类定义步骤:
 * 1、添加MapStruct jar包依赖
 * 2、新增接口或抽象类,并且使用org.mapstruct.Mapper注解标签修饰。
 * 3、添加自定义转换方法
 */
@Mapper
public interface PersonMapper 
    PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);

    PersonDTO conver(Person person);

    @Mapping(source = "name", target="personName")
    PersonDTO converMapping(Person person);

测试:

    public static void main(String[] args) throws ParseException 
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");

        Person person = Person.builder().age(31).createTime(format.parse("1991-12-20")).id("1").describe("Java 开发")
                .height(180L).name("在奋斗的大道上").source(new BigDecimal(10000)).build();

        PersonDTO personDTO = PersonMapper.INSTANCT.converMapping(person);
        System.out.println(personDTO);
    

执行效果:

PersonDTO(describe=Java 开发, id=1, personName=在奋斗的大道上, age=31, source=10000, height=180.0)

MapStruct 转换总结:

  1. 当一个属性与其目标实体对应的名称相同时,它将被隐式映射。

  2. 当属性在目标实体中具有不同的名称时,可以通过@Mapping注释指定其名称。

温馨提示:

如果映射的对象field name不一样,通过 @Mapping 指定。
如果忽略映射字段加@Mapping#ignore() = true 

 2.2 MapStruct 指定默认值

功能要求:Person 类实例对象 转换PersonDto 类实例对象时,将describe属性值设置为:"默认值"。

在@Mapping注解标签中必须添加target属性,source属性,默认值使用defaultValue属性设置。

package com.zzg.mapstruct.mapper;

import com.zzg.mapstruct.entity.Person;
import com.zzg.mapstruct.entity.PersonDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

/**
 * mapstruct 工具类定义步骤:
 * 1、添加MapStruct jar包依赖
 * 2、新增接口或抽象类,并且使用org.mapstruct.Mapper注解标签修饰。
 * 3、添加自定义转换方法
 */
@Mapper
public interface PersonMapper 
    PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);

    PersonDTO conver(Person person);

    @Mapping(source = "name", target="personName")
    @Mapping(source = "describe", target = "describe", defaultValue = "默认值")
    PersonDTO converMapping(Person person);

测试:

    public static void main(String[] args) throws ParseException 
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");

        Person person = Person.builder().age(31).createTime(format.parse("1991-12-20")).id("1").describe("Java 开发")
                .height(180L).name("在奋斗的大道上").source(new BigDecimal(10000)).build();

        PersonDTO personDTO = PersonMapper.INSTANCT.converMapping(person);
        System.out.println(personDTO);
    

执行效果:

PersonDTO(describe=Java 开发, id=1, personName=在奋斗的大道上, age=31, source=10000, height=180.0)

通过Main 方法测试发现PersonDto 类对象属性describe默认值未生效。将Person.buildr 建造模式移除describe属性值设置。

    public static void main(String[] args) throws ParseException 
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");

        Person person = Person.builder().age(31).createTime(format.parse("1991-12-20")).id("1")
                .height(180L).name("在奋斗的大道上").source(new BigDecimal(10000)).build();

        PersonDTO personDTO = PersonMapper.INSTANCT.converMapping(person);
        System.out.println(personDTO);
    

执行效果:

PersonDTO(describe=默认值, id=1, personName=在奋斗的大道上, age=31, source=10000, height=180.0)

2.3 MapStruct 表达式

功能要求:在PersonDto 类中添加时间属性currentDay,并且要求赋值系统当前时间

在@Mapping注解标签中必须添加target属性 和expression属性。

package com.zzg.mapstruct.entity;

import lombok.Data;

import java.util.Date;

@Data
public class PersonDTO 
    String describe;

    private Long id;

    private String personName;

    private String age;

    private String source;

    private String height;

    private Date currentDay;

package com.zzg.mapstruct.mapper;

import com.zzg.mapstruct.entity.Person;
import com.zzg.mapstruct.entity.PersonDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

/**
 * mapstruct 工具类定义步骤:
 * 1、添加MapStruct jar包依赖
 * 2、新增接口或抽象类,并且使用org.mapstruct.Mapper注解标签修饰。
 * 3、添加自定义转换方法
 */
@Mapper
public interface PersonMapper 
    PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);

    @Mapping(target = "currentDay", expression = "java(new java.util.Date())")
    PersonDTO conver(Person person);

    @Mapping(source = "name", target="personName")
    @Mapping(source = "describe", target = "describe", defaultValue = "默认值")
    PersonDTO converMapping(Person person);

测试:

 public static void main(String[] args) throws ParseException 
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");

        Person person = Person.builder().age(31).createTime(format.parse("1991-12-20")).id("1").describe("Java 开发")
                .height(180L).name("在奋斗的大道上").source(new BigDecimal(10000)).build();

        PersonDTO personDTO = PersonMapper.INSTANCT.conver(person);
        System.out.println(personDTO);
    

执行结果:

PersonDTO(describe=Java 开发, id=1, personName=null, age=31, source=10000, height=180.0, currentDay=Wed Oct 19 17:20:23 CST 2022)

温馨提示:

1、expression()属性不能与source()、defaultValue()、defaultExpression()、qualifiedBy()、qualifiedByName()或constant()一起使用。

2、默认表达式@Mapping#defaultExpression()是默认值和表达式的组合。仅当source属性为null时才使用它们

2.4 MapStruct 时间格式

功能要求:指定PersonDto 类属性currentDay的时间格式为'yyyy-MM-dd'

在@Mapping注解标签中必须添加target属性、source属性 和dateFormat属性。

package com.zzg.mapstruct.mapper;

import com.zzg.mapstruct.entity.Person;
import com.zzg.mapstruct.entity.PersonDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

/**
 * mapstruct 工具类定义步骤:
 * 1、添加MapStruct jar包依赖
 * 2、新增接口或抽象类,并且使用org.mapstruct.Mapper注解标签修饰。
 * 3、添加自定义转换方法
 */
@Mapper
public interface PersonMapper 
    PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);

    @Mapping(target = "currentDay", expression = "java(new java.util.Date())")
    PersonDTO conver(Person person);

    @Mapping(source = "name", target="personName")
    @Mapping(source = "describe", target = "describe", defaultValue = "默认值")
    @Mapping(source = "createTime", target = "currentDay", dateFormat = "yyyy-MM-dd")
    PersonDTO converMapping(Person person);

测试:

  public static void main(String[] args) throws ParseException 
        SimpleDateFormat format = new SimpleDateFormat("yyyy");

        Person person = Person.builder().age(31).createTime(format.parse("1991")).id("1").describe("Java 开发")
                .height(180L).name("在奋斗的大道上").source(new BigDecimal(10000)).build();

        PersonDTO personDTO = PersonMapper.INSTANCT.converMapping(person);

        System.out.println(personDTO);
    

执行效果:

PersonDTO(describe=Java 开发, id=1, personName=在奋斗的大道上, age=31, source=10000, height=180.0, currentDay=Tue Jan 01 00:00:00 CST 1991)

2.5 MapStruct 数字格式

功能要求:指定PersonDto 类属性age的数字格式为'#0.00'

在@Mapping注解标签中必须添加target属性、source属性 和numberFormat属性。

package com.zzg.mapstruct.mapper;

import com.zzg.mapstruct.entity.Person;
import com.zzg.mapstruct.entity.PersonDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

/**
 * mapstruct 工具类定义步骤:
 * 1、添加MapStruct jar包依赖
 * 2、新增接口或抽象类,并且使用org.mapstruct.Mapper注解标签修饰。
 * 3、添加自定义转换方法
 */
@Mapper
public interface PersonMapper 
    PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);

    @Mapping(target = "currentDay", expression = "java(new java.util.Date())")
    PersonDTO conver(Person person);

    @Mapping(source = "name", target="personName")
    @Mapping(source = "describe", target = "describe", defaultValue = "默认值")
    @Mapping(source = "createTime", target = "currentDay", dateFormat = "yyyy-MM-dd")
    @Mapping(source = "age", target = "age", numberFormat = "#0.00")
    PersonDTO converMapping(Person person);

测试:

    public static void main(String[] args) throws ParseException 
        SimpleDateFormat format = new SimpleDateFormat("yyyy");

        Person person = Person.builder().age(31).createTime(format.parse("1991")).id("1").describe("Java 开发")
                .height(180L).name("在奋斗的大道上").source(new BigDecimal(10000)).build();

        PersonDTO personDTO = PersonMapper.INSTANCT.converMapping(person);

        System.out.println(personDTO);
    

执行效果:

PersonDTO(describe=Java 开发, id=1, personName=在奋斗的大道上, age=31.00, source=10000, height=180.0, currentDay=Tue Jan 01 00:00:00 CST 1991)

3、MapStruct 组合映射

3.1 多参数源映射

功能要求:新增一个类对象(BasicEntity),要求将BasicEntity中的createTime 属性赋值给PersonDto 类中的currentDay属性。

package com.zzg.mapstruct.entity;

import lombok.Builder;
import lombok.Data;
import java.util.Date;

@Data
@Builder
public class BasicEntity 
    private Date createTime;

    private String createBy;

    private Date updateTime;

    private String updateBy;

    private int _ROW;

PersonMapper 接口新增多参数源映射接口定义。
package com.zzg.mapstruct.mapper;

import com.zzg.mapstruct.entity.BasicEntity;
import com.zzg.mapstruct.entity.Person;
import com.zzg.mapstruct.entity.PersonDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

/**
 * mapstruct 工具类定义步骤:
 * 1、添加MapStruct jar包依赖
 * 2、新增接口或抽象类,并且使用org.mapstruct.Mapper注解标签修饰。
 * 3、添加自定义转换方法
 */
@Mapper
public interface PersonMapper 
    PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);

    @Mapping(target = "currentDay", expression = "java(new java.util.Date())")
    PersonDTO conver(Person person);

    @Mapping(source = "name", target="personName")
    @Mapping(source = "describe", target = "describe", defaultValue = "默认值")
    @Mapping(source = "createTime", target = "currentDay", dateFormat = "yyyy-MM-dd")
    @Mapping(source = "age", target = "age", numberFormat = "#0.00")
    PersonDTO converMapping(Person person);

    /**
     * 多参数源映射转换
     * @param person
     * @param basicEntity
     * @return
     */
    @Mapping(source = "basicEntity.createTime", target = "currentDay")
    @Mapping(source = "person.name", target="personName")
    PersonDTO combinationConver(Person person, BasicEntity basicEntity);

测试:

    public static void main(String[] args) throws ParseException 
        SimpleDateFormat format = new SimpleDateFormat("yyyy");

        Person person = Person.builder().age(31).createTime(format.parse("1991")).id("1").describe("Java 开发")
                .height(180L).name("在奋斗的大道上").source(new BigDecimal(10000)).build();
        BasicEntity basicEntity = BasicEntity.builder().createTime(new Date()).createBy("周志刚").updateTime(new Date()).updateBy("周晨曦")._ROW(1).build();
        PersonDTO personDTO = PersonMapper.INSTANCT.combinationConver(person, basicEntity);

        System.out.println(personDTO);
    

执行效果:

PersonDTO(describe=Java 开发, id=1, personName=在奋斗的大道上, age=31, source=10000, height=180.0, currentDay=Wed Oct 19 23:29:59 CST 2022)

3.2 使用其他参数值

功能要求:PersonDTO l类中的id 属性值要求从方法中传入,不接受转换类Person 中的id 属性

PersonMapper 接口新增其他参数值映射接口定义。

package com.zzg.mapstruct.mapper;

import com.zzg.mapstruct.entity.BasicEntity;
import com.zzg.mapstruct.entity.Person;
import com.zzg.mapstruct.entity.PersonDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

/**
 * mapstruct 工具类定义步骤:
 * 1、添加MapStruct jar包依赖
 * 2、新增接口或抽象类,并且使用org.mapstruct.Mapper注解标签修饰。
 * 3、添加自定义转换方法
 */
@Mapper
public interface PersonMapper 
    PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);

    @Mapping(target = "currentDay", expression = "java(new java.util.Date())")
    PersonDTO conver(Person person);

    @Mapping(source = "name", target="personName")
    @Mapping(source = "describe", target = "describe", defaultValue = "默认值")
    @Mapping(source = "createTime", target = "currentDay", dateFormat = "yyyy-MM-dd")
    @Mapping(source = "age", target = "age", numberFormat = "#0.00")
    PersonDTO converMapping(Person person);

    /**
     * 多参数源映射转换
     * @param person
     * @param basicEntity
     * @return
     */
    @Mapping(source = "basicEntity.createTime", target = "currentDay")
    @Mapping(source = "person.name", target="personName")
    PersonDTO combinationConver(Person person, BasicEntity basicEntity);

    /**
     * 其他参数值映射
     * @param person
     * @param id
     * @return
     */
    @Mapping(source = "person.name", target="personName")
    @Mapping(target = "id", source = "id")
    PersonDTO mapTo(Person person, String id);

测试:

    public static void main(String[] args) throws ParseException 
        SimpleDateFormat format = new SimpleDateFormat("yyyy");

        Person person = Person.builder().age(31).createTime(format.parse("1991")).id("1").describe("Java 开发")
                .height(180L).name("在奋斗的大道上").source(new BigDecimal(10000)).build();

        PersonDTO personDTO = PersonMapper.INSTANCT.mapTo(person, "10");

        System.out.println(personDTO);
    

效果截图:

PersonDTO(describe=Java 开发, id=10, personName=在奋斗的大道上, age=31, source=10000, height=180.0, currentDay=null)

温馨提示:在进行其他参数值映射转换时,主要source 对象与tager 对象的Mapping 映射需要添加实例对象名称 .属性名称。

 3.3 嵌套映射

功能要求:为 Person 类新增一个类属性Company 公司属性,为PersonDTO类新增一个类型属性CompanyDTO公司属性。

package com.zzg.mapstruct.entity;

import lombok.Data;
import java.util.Date;

@Data
public class Company 
    public String name;
    public String address;
    public Integer numberOfPeople;
    public Date createDate;
    public String createBy;

package com.zzg.mapstruct.entity;

import lombok.Builder;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;

@Data
@Builder
public class Person 
    String describe;

    private String id;

    private String name;

    private int age;

    private BigDecimal source;

    private double height;

    private Date createTime;

    // 添加Company 类属性
    private Company company;

package com.zzg.mapstruct.entity;

import lombok.Data;
import java.util.Date;

@Data
public class CompanyDTO 
    public String name;
    public String address;
    public Integer numbers ;
    public Date currentDate;
    public String leader;

package com.zzg.mapstruct.entity;

import lombok.Data;

import java.util.Date;

@Data
public class PersonDTO 
    String describe;

    private Long id;

    private String personName;

    private String age;

    private String source;

    private String height;

    private Date currentDay;

    // 添加CompanyDTO 类属性
    private CompanyDTO dto;

PersonMapper 接口新增嵌套属性接口转换定义

package com.zzg.mapstruct.mapper;

import com.zzg.mapstruct.entity.*;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

/**
 * mapstruct 工具类定义步骤:
 * 1、添加MapStruct jar包依赖
 * 2、新增接口或抽象类,并且使用org.mapstruct.Mapper注解标签修饰。
 * 3、添加自定义转换方法
 */
@Mapper
public interface PersonMapper 
    PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);

    @Mapping(target = "currentDay", expression = "java(new java.util.Date())")
    PersonDTO conver(Person person);

    @Mapping(source = "name", target="personName")
    @Mapping(source = "describe", target = "describe", defaultValue = "默认值")
    @Mapping(source = "createTime", target = "currentDay", dateFormat = "yyyy-MM-dd")
    @Mapping(source = "age", target = "age", numberFormat = "#0.00")
    PersonDTO converMapping(Person person);

    /**
     * 多参数源映射转换
     * @param person
     * @param basicEntity
     * @return
     */
    @Mapping(source = "basicEntity.createTime", target = "currentDay")
    @Mapping(source = "person.name", target="personName")
    PersonDTO combinationConver(Person person, BasicEntity basicEntity);

    /**
     * 方法值映射
     * @param person
     * @param id
     * @return
     */
    @Mapping(source = "person.name", target="personName")
    @Mapping(target = "id", source = "id")
    PersonDTO mapTo(Person person, String id);

    /**
     * 嵌套类属性转换
     * @param person
     * @return
     */
    @Mapping(source="person.company", target = "dto")
    PersonDTO coverNestedProperties(Person person);

    CompanyDTO converDto(Company company);

测试:

  public static void main(String[] args) throws ParseException 
        SimpleDateFormat format = new SimpleDateFormat("yyyy");
        Company company = new Company();
        company.setAddress("龙岗大道10012");
        company.setName("成宇科技");
        company.setCreateBy("周晨宇");
        company.setCreateDate(new Date());
        company.setNumberOfPeople(1000);

        Person person = Person.builder().age(31).createTime(format.parse("1991")).id("1").describe("Java 开发")
                .height(180L).name("在奋斗的大道上").source(new BigDecimal(10000)).company(company).build();

        PersonDTO personDTO = PersonMapper.INSTANCT.coverNestedProperties(person);

        System.out.println(personDTO);
    

执行结果:

PersonDTO(describe=Java 开发, id=1, personName=null, age=31, source=10000, height=180.0, currentDay=null, dto=CompanyDTO(name=成宇科技, address=龙岗大道10012, numbers=null, currentDate=null, leader=null))

嵌套属性问题:在测试上述功能代码发现,Person类中的Company类属性转换为PersonDTO类中的CompanyDTO 类属性时,发生有属性无法一一对应导致属性值为空 ,解决此问题的办法时,在@Mapper接口  中添加一个自定义 方法,主要解决Company 转CompanyDTO,无法对应的字段使用@Mapping 注解标签实现对应转换。

package com.zzg.mapstruct.mapper;

import com.zzg.mapstruct.entity.*;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

/**
 * mapstruct 工具类定义步骤:
 * 1、添加MapStruct jar包依赖
 * 2、新增接口或抽象类,并且使用org.mapstruct.Mapper注解标签修饰。
 * 3、添加自定义转换方法
 */
@Mapper
public interface PersonMapper 
    PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);

    @Mapping(target = "currentDay", expression = "java(new java.util.Date())")
    PersonDTO conver(Person person);

    @Mapping(source = "name", target="personName")
    @Mapping(source = "describe", target = "describe", defaultValue = "默认值")
    @Mapping(source = "createTime", target = "currentDay", dateFormat = "yyyy-MM-dd")
    @Mapping(source = "age", target = "age", numberFormat = "#0.00")
    PersonDTO converMapping(Person person);

    /**
     * 多参数源映射转换
     * @param person
     * @param basicEntity
     * @return
     */
    @Mapping(source = "basicEntity.createTime", target = "currentDay")
    @Mapping(source = "person.name", target="personName")
    PersonDTO combinationConver(Person person, BasicEntity basicEntity);

    /**
     * 方法值映射
     * @param person
     * @param id
     * @return
     */
    @Mapping(source = "person.name", target="personName")
    @Mapping(target = "id", source = "id")
    PersonDTO mapTo(Person person, String id);

    /**
     * 嵌套类属性转换
     * @param person
     * @return
     */
    @Mapping(source="person.company", target = "dto")
    PersonDTO coverNestedProperties(Person person);

    @Mapping(source = "createBy", target = "leader")
    @Mapping(source = "createDate", target = "currentDate")
    @Mapping(source = "numberOfPeople", target = "numbers")
    CompanyDTO converDto(Company company);

测试:

    public static void main(String[] args) throws ParseException 
        SimpleDateFormat format = new SimpleDateFormat("yyyy");
        Company company = new Company();
        company.setAddress("龙岗大道10012");
        company.setName("成宇科技");
        company.setCreateBy("周晨宇");
        company.setCreateDate(new Date());
        company.setNumberOfPeople(1000);

        Person person = Person.builder().age(31).createTime(format.parse("1991")).id("1").describe("Java 开发")
                .height(180L).name("在奋斗的大道上").source(new BigDecimal(10000)).company(company).build();

        PersonDTO personDTO = PersonMapper.INSTANCT.coverNestedProperties(person);

        System.out.println(personDTO);
    

效果截图:

PersonDTO(describe=Java 开发, id=1, personName=null, age=31, source=10000, height=180.0, currentDay=null, dto=CompanyDTO(name=成宇科技, address=龙岗大道10012, numbers=1000, currentDate=Thu Oct 20 19:45:17 CST 2022, leader=周晨宇))

 拓展:嵌套属性为集合属性对象时,MapStruct 如何完成转换?

功能要求:为 Person 类新增一个集合属性List<House> 房屋属性,为PersonDTO类新增一个Lis<HouseDTO>房屋属性。

package com.zzg.mapstruct.entity;

import lombok.Data;

@Data
public class House 
    private String address;
    private Integer price;

package com.zzg.mapstruct.entity;

import lombok.Data;

@Data
public class HouseDTO 
    private Integer house_pay;
    private String address;

Person 和PersonDTO 分别添加相关集合属性:

    // 添加集合属性
    private List<House> houses;
   // 添加集合属性
    private List<HouseDTO> houseDTOs;

测试:

  public static void main(String[] args) throws ParseException 
        SimpleDateFormat format = new SimpleDateFormat("yyyy");
        Company company = new Company();
        company.setAddress("龙岗大道10012");
        company.setName("成宇科技");
        company.setCreateBy("周晨宇");
        company.setCreateDate(new Date());
        company.setNumberOfPeople(1000);

        List<House> houses= new ArrayList<>();
        House one = new House();
        one.setPrice(1000000);
        one.setAddress("湖南长沙");

        House two = new House();
        two.setPrice(2600000);
        two.setAddress("广东深圳");
        houses.add(one);
        houses.add(two);

        Person person = Person.builder().age(31).createTime(format.parse("1991")).id("1").describe("Java 开发")
                .height(180L).name("在奋斗的大道上").source(new BigDecimal(10000)).company(company).houses(houses).build();

        PersonDTO personDTO = PersonMapper.INSTANCT.coverNestedColl(person);

        System.out.println(personDTO);
    

测试结果:

PersonDTO(describe=Java 开发, id=1, personName=null, age=31, source=10000, height=180.0, currentDay=null, dto=CompanyDTO(name=成宇科技, address=龙岗大道10012, numbers=1000, currentDate=Thu Oct 20 20:00:48 CST 2022, leader=周晨宇), houseDTOs=null)

测试问题:

我们发现Person类中List<House> 属性没有正确转换为PersonDTO 类中的List<HouseDTO>属性。

解决办法:在PersonMapper接口中新增集合属性转换方法coverColl。并在coverNestedColl方法上添加集合属性映射转换配置。

 @Mapping(source = "person.houses", target ="houseDTOs")
package com.zzg.mapstruct.mapper;

import com.zzg.mapstruct.entity.*;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

import java.util.List;

/**
 * mapstruct 工具类定义步骤:
 * 1、添加MapStruct jar包依赖
 * 2、新增接口或抽象类,并且使用org.mapstruct.Mapper注解标签修饰。
 * 3、添加自定义转换方法
 */
@Mapper
public interface PersonMapper 
    PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);

    @Mapping(target = "currentDay", expression = "java(new java.util.Date())")
    PersonDTO conver(Person person);

    @Mapping(source = "name", target="personName")
    @Mapping(source = "describe", target = "describe", defaultValue = "默认值")
    @Mapping(source = "createTime", target = "currentDay", dateFormat = "yyyy-MM-dd")
    @Mapping(source = "age", target = "age", numberFormat = "#0.00")
    PersonDTO converMapping(Person person);

    /**
     * 多参数源映射转换
     * @param person
     * @param basicEntity
     * @return
     */
    @Mapping(source = "basicEntity.createTime", target = "currentDay")
    @Mapping(source = "person.name", target="personName")
    PersonDTO combinationConver(Person person, BasicEntity basicEntity);

    /**
     * 方法值映射
     * @param person
     * @param id
     * @return
     */
    @Mapping(source = "person.name", target="personName")
    @Mapping(target = "id", source = "id")
    PersonDTO mapTo(Person person, String id);

    /**
     * 嵌套类属性转换
     * @param person
     * @return
     */
    @Mapping(source="person.company", target = "dto")
    PersonDTO coverNestedProperties(Person person);

    @Mapping(source = "createBy", target = "leader")
    @Mapping(source = "createDate", target = "currentDate")
    @Mapping(source = "numberOfPeople", target = "numbers")
    CompanyDTO converDto(Company company);


    @Mapping(source="person.company", target = "dto")
    @Mapping(source = "person.houses", target ="houseDTOs")
    PersonDTO coverNestedColl(Person person);
    
    List<HouseDTO> coverColl(List<House> houses);

测试结果:

 public static void main(String[] args) throws ParseException 
        SimpleDateFormat format = new SimpleDateFormat("yyyy");
        Company company = new Company();
        company.setAddress("龙岗大道10012");
        company.setName("成宇科技");
        company.setCreateBy("周晨宇");
        company.setCreateDate(new Date());
        company.setNumberOfPeople(1000);

        List<House> houses= new ArrayList<>();
        House one = new House();
        one.setPrice(1000000);
        one.setAddress("湖南长沙");

        House two = new House();
        two.setPrice(2600000);
        two.setAddress("广东深圳");
        houses.add(one);
        houses.add(two);

        Person person = Person.builder().age(31).createTime(format.parse("1991")).id("1").describe("Java 开发")
                .height(180L).name("在奋斗的大道上").source(new BigDecimal(10000)).company(company).houses(houses).build();

        PersonDTO personDTO = PersonMapper.INSTANCT.coverNestedColl(person);

        System.out.println(personDTO);
    

测试结果:

PersonDTO(describe=Java 开发, id=1, personName=null, age=31, source=10000, height=180.0, currentDay=null, dto=CompanyDTO(name=成宇科技, address=龙岗大道10012, numbers=1000, currentDate=Fri Oct 21 09:33:17 CST 2022, leader=周晨宇), houseDTOs=[HouseDTO(house_pay=null, address=湖南长沙), HouseDTO(house_pay=null, address=广东深圳)])

  3.4 逆映射

大家都发现了之前的案列,我们都是使用PO 对象转DTO对象,同时希望支持DTO对象转PO对象,MapStruct也支持PO 和DTO双向映射,主要通过@InheritInverseConfiguration注解标签实现。

@InheritInverseConfiguration表示方法应继承相应反向方法的反向配置

温馨提示:建议指定name属性,对应 逆映射方法名称。

package com.zzg.mapstruct.mapper;

import com.zzg.mapstruct.entity.*;
import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

import java.util.List;

/**
 * mapstruct 工具类定义步骤:
 * 1、添加MapStruct jar包依赖
 * 2、新增接口或抽象类,并且使用org.mapstruct.Mapper注解标签修饰。
 * 3、添加自定义转换方法
 */
@Mapper
public interface PersonMapper 
    PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);

    @Mapping(target = "currentDay", expression = "java(new java.util.Date())")
    PersonDTO conver(Person person);

    @Mapping(source = "name", target="personName")
    @Mapping(source = "describe", target = "describe", defaultValue = "默认值")
    @Mapping(source = "createTime", target = "currentDay", dateFormat = "yyyy-MM-dd")
    @Mapping(source = "age", target = "age", numberFormat = "#0.00")
    PersonDTO converMapping(Person person);

    /**
     * 多参数源映射转换
     * @param person
     * @param basicEntity
     * @return
     */
    @Mapping(source = "basicEntity.createTime", target = "currentDay")
    @Mapping(source = "person.name", target="personName")
    PersonDTO combinationConver(Person person, BasicEntity basicEntity);

    /**
     * 方法值映射
     * @param person
     * @param id
     * @return
     */
    @Mapping(source = "person.name", target="personName")
    @Mapping(target = "id", source = "id")
    PersonDTO mapTo(Person person, String id);

    /**
     * 嵌套类属性转换
     * @param person
     * @return
     */
    @Mapping(source="person.company", target = "dto")
    PersonDTO coverNestedProperties(Person person);

    @Mapping(source = "createBy", target = "leader")
    @Mapping(source = "createDate", target = "currentDate")
    @Mapping(source = "numberOfPeople", target = "numbers")
    CompanyDTO converDto(Company company);


    @Mapping(source="person.company", target = "dto")
    @Mapping(source = "person.houses", target ="houseDTOs")
    PersonDTO coverNestedColl(Person person);

    List<HouseDTO> coverColl(List<House> houses);

    /**
     * 逆映射转换
     * @param personDTO
     * @return
     */
    @InheritInverseConfiguration(name = "coverNestedColl")
    Person coverNestedColl(PersonDTO personDTO);

测试代码:

 public static void main(String[] args) throws ParseException 
        SimpleDateFormat format = new SimpleDateFormat("yyyy");
        CompanyDTO company = new CompanyDTO();
        company.setAddress("龙岗大道10012");
        company.setName("成宇科技");
        company.setLeader("周晨宇");
        company.setCurrentDate(new Date());
        company.setNumbers(1000);

        List<HouseDTO> houses= new ArrayList<>();
        HouseDTO one = new HouseDTO();
        one.setHouse_pay(1000000);
        one.setAddress("湖南长沙");

        HouseDTO two = new HouseDTO();
        two.setHouse_pay(2600000);
        two.setAddress("广东深圳");
        houses.add(one);
        houses.add(two);

        PersonDTO person = new PersonDTO();
        person.setAge("31");
        person.setCurrentDay(format.parse("1991"));
        person.setId(1l);
        person.setDescribe("Java 开发");
        person.setHeight("180");
        person.setPersonName("在奋斗的大道上");
        person.setSource("10000");
        person.setDto(company);
        person.setHouseDTOs(houses);

        Person personDTO = PersonMapper.INSTANCT.coverNestedColl(person);

        System.out.println(personDTO);
    

测试结果:

Person(describe=Java 开发, id=1, name=null, age=31, source=10000, height=180.0, createTime=null, company=Company(name=成宇科技, address=龙岗大道10012, numberOfPeople=null, createDate=null, createBy=null), houses=[House(address=湖南长沙, price=null), House(address=广东深圳, price=null)])

  3.4 继承映射

问题抛出:MapStruct 提供了很多方法级注解标签:Mapping,@BeanMapping,@IterableMapping 等等。有时候我们希望某些转换方法继承指定转换方法的MapStruct 注解标签配置。以节省@Mapper 接口到处都是相同的注解标签。

我们可以通过@InheritConfiguration注解标签,实现我们希望的功能。

通过声明@InheritConfiguration该方法,MapStruct可以搜索继承候选,以应用继承自该方法的注释。

白话讲解:

如果所有类型的A(源类型和结果类型)都可以分配给B的相应类型,则一个方法A可以从另一种方法B继承配置。如果可以使用多个方法作为继承的源,则必须在注释中指定方法名称:@InheritConfiguration( name = “coverNestedColl” )。 

 示例:在@Mapper 接口中添加配置继承方法

package com.zzg.mapstruct.mapper;

import com.zzg.mapstruct.entity.*;
import org.mapstruct.*;
import org.mapstruct.factory.Mappers;

import java.util.List;

/**
 * mapstruct 工具类定义步骤:
 * 1、添加MapStruct jar包依赖
 * 2、新增接口或抽象类,并且使用org.mapstruct.Mapper注解标签修饰。
 * 3、添加自定义转换方法
 */
@Mapper
public interface PersonMapper 
    PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);

    @Mapping(target = "currentDay", expression = "java(new java.util.Date())")
    PersonDTO conver(Person person);

    @Mapping(source = "name", target="personName")
    @Mapping(source = "describe", target = "describe", defaultValue = "默认值")
    @Mapping(source = "createTime", target = "currentDay", dateFormat = "yyyy-MM-dd")
    @Mapping(source = "age", target = "age", numberFormat = "#0.00")
    PersonDTO converMapping(Person person);

    /**
     * 多参数源映射转换
     * @param person
     * @param basicEntity
     * @return
     */
    @Mapping(source = "basicEntity.createTime", target = "currentDay")
    @Mapping(source = "person.name", target="personName")
    PersonDTO combinationConver(Person person, BasicEntity basicEntity);

    /**
     * 方法值映射
     * @param person
     * @param id
     * @return
     */
    @Mapping(source = "person.name", target="personName")
    @Mapping(target = "id", source = "id")
    PersonDTO mapTo(Person person, String id);

    /**
     * 嵌套类属性转换
     * @param person
     * @return
     */
    @Mapping(source="person.company", target = "dto")
    PersonDTO coverNestedProperties(Person person);

    @Mapping(source = "createBy", target = "leader")
    @Mapping(source = "createDate", target = "currentDate")
    @Mapping(source = "numberOfPeople", target = "numbers")
    CompanyDTO converDto(Company company);


    @Mapping(source="person.company", target = "dto")
    @Mapping(source = "person.houses", target ="houseDTOs")
    PersonDTO coverNestedColl(Person person);

    List<HouseDTO> coverColl(List<House> houses);

    /**
     * 逆映射转换
     * @param personDTO
     * @return
     */
    @InheritInverseConfiguration(name = "coverNestedColl")
    Person coverNestedColl(PersonDTO personDTO);

    /**
     * 配置继承
     * @param person
     * @param personDTO
     */
    @InheritConfiguration(name="coverNestedColl")
    void coverExtend(Person person, @MappingTarget PersonDTO personDTO);

测试: 

    public static void main(String[] args) throws ParseException 
        SimpleDateFormat format = new SimpleDateFormat("yyyy");
        Company company = new Company();
        company.setAddress("龙岗大道10012");
        company.setName("成宇科技");
        company.setCreateBy("周晨宇");
        company.setCreateDate(new Date());
        company.setNumberOfPeople(1000);

        List<House> houses= new ArrayList<>();
        House one = new House();
        one.setPrice(1000000);
        one.setAddress("湖南长沙");

        House two = new House();
        two.setPrice(2600000);
        two.setAddress("广东深圳");
        houses.add(one);
        houses.add(two);

        Person person = Person.builder().age(31).createTime(format.parse("1991")).id("1").describe("Java 开发")
                .height(180L).name("在奋斗的大道上").source(new BigDecimal(10000)).company(company).houses(houses).build();
        PersonDTO personDTO = new PersonDTO();

        PersonMapper.INSTANCT.coverExtend(person, personDTO);

        System.out.println(personDTO);
    

 测试结果:

PersonDTO(describe=Java 开发, id=1, personName=null, age=31, source=10000, height=180.0, currentDay=null, dto=CompanyDTO(name=成宇科技, address=龙岗大道10012, numbers=1000, currentDate=Fri Oct 21 10:20:37 CST 2022, leader=周晨宇), houseDTOs=[HouseDTO(house_pay=null, address=湖南长沙), HouseDTO(house_pay=null, address=广东深圳)])

 3.5 共享映射

开发实例:我们经常在数据库中定义 创建时间date_create、修改时间date_update 这样的字段,通常情况下几乎每个表都存在这样的字段,数据库里面的类型是java.sql.Timestamp类型,而我们一般都转为String类型便于显示。如果不用mapStruct的共享配置,那相当于在每个表对应的转化类里面配置 Timestamp到String的映射。但是如果有共享配置,我们只要配置一遍,然后在其他地方引入,达到共享的目的。

  mapStruct的共享映射通过注解 @MapperConfig定义,然后在@Mapper的属性config中引入。@MapperConfig注释具有与@Mapper注释相同的属性。任何未通过@Mapper指定的属性都将从共享配置中继承。在@Mapper中指定的属性优先于通过引用的配置类指定的属性。

示例:共享映射

package com.zzg.mapstruct.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class BaseDTO 
    public Date sysCreateDate;
    public String sysCreateId;

package com.zzg.mapstruct.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class BasePO 
    public String sysCreateDate;

    public String sysCreateId;

package com.zzg.mapstruct.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User extends BasePO 
    private String name;
    private String phone;
    private Date birthday;


package com.zzg.mapstruct.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserDTO extends BaseDTO
    private String realname;
    private String telephone;
    private Date birthday;


通用@MapperConfig 定义和@Mapper定义。

package com.zzg.mapstruct.config;

import com.zzg.mapstruct.entity.BaseDTO;
import com.zzg.mapstruct.entity.BasePO;
import com.zzg.mapstruct.util.DateFormtUtil;
import org.mapstruct.MapperConfig;
import org.mapstruct.Mapping;
import org.mapstruct.ReportingPolicy;

@MapperConfig(unmappedTargetPolicy = ReportingPolicy.ERROR, uses = DateFormtUtil.class)
public interface CommonConfig 
    @Mapping(source = "sysCreateDate", target = "sysCreateDate")
    BaseDTO converBase(BasePO basePO);
package com.zzg.mapstruct.util;

import org.mapstruct.Named;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateFormtUtil 

    public Date asDate(String date) 
        try 
            return date != null ? new SimpleDateFormat( "yyyy-MM-dd" )
                    .parse( date ) : null;
        
        catch ( ParseException e ) 
            throw new RuntimeException( e );
        
    

package com.zzg.mapstruct.mapper;


import com.zzg.mapstruct.config.CommonConfig;
import com.zzg.mapstruct.entity.User;
import com.zzg.mapstruct.entity.UserDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

@Mapper(config = CommonConfig.class)
public interface UserMapper 
    UserMapper INSTANCT = Mappers.getMapper(UserMapper.class);

    @Mapping(source = "name", target="realname")
    @Mapping(source = "phone", target="telephone")
    UserDTO converMapping(User user);

测试代码:

package com.zzg.mapstruct.test;

import com.zzg.mapstruct.entity.User;
import com.zzg.mapstruct.entity.UserDTO;
import com.zzg.mapstruct.mapper.UserMapper;

import java.text.ParseException;
import java.text.SimpleDateFormat;

public class TwoTest 
    public static void main(String[] args) throws ParseException 
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        User user = new User();
        user.setSysCreateDate("2022-10-21");
        user.setSysCreateId("1L");
        user.setName("周晨曦");
        user.setBirthday(format.parse("2019-05-30"));
        user.setPhone("119");

        UserDTO userDTO = UserMapper.INSTANCT.converMapping(user);
        System.out.println(userDTO);
        System.out.println(userDTO.getSysCreateDate());
        System.out.println(userDTO.getSysCreateId());
    

测试结果:

UserDTO(realname=周晨曦, telephone=119, birthday=Thu May 30 00:00:00 CST 2019)
Fri Oct 21 00:00:00 CST 2022
1L

3.6 自定义方法

3.6.1 自定义类型转换方法

public class DateMapper 

    public String asString(Date date) 
        return date != null ? new SimpleDateFormat( "yyyy-MM-dd" )
            .format( date ) : null;
    

    public Date asDate(String date) 
        try 
            return date != null ? new SimpleDateFormat( "yyyy-MM-dd" )
                .parse( date ) : null;
        
        catch ( ParseException e ) 
            throw new RuntimeException( e );
        
    


@Mapper(uses=DateMapper.class)
public interface PersonMapper
  PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);
  PersonDTO conver(Person person);


温馨提示: @Mapper#uses可以使用多个类

3.6.2 使用@Qualifier

@Qualifier标记的自定义注解标记的方法,必须有输入, 否则编译时会抛出异常。

public class DateFormtUtil 

    @DateFormat
    public static String dateToString(Date date)
        return date == null ? "": new SimpleDateFormat("yyyy-MM-dd").format(date);
    

    @Qualifier
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.CLASS)
    public @interface DateFormat



@Mapper(uses =DateFormtUtil.class)
public interface PersonMapper 

    PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);

    @Mapping(target = "createTime",source = "createTime",qualifiedBy = DateFormat.class)
    PersonDTO conver(Person person);



3.6.3  使用@namd

public class DateFormtUtil 

    @Named("dateToString")
    public static String dateToString(Date date)
        return date == null ? "": new SimpleDateFormat("yyyy-MM-dd").format(date);
    


@Mapper(uses =DateFormtUtil.class)
public interface PersonMapper 

    PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);

   @Mapping(target = "createTime",source = "createTime",qualifiedByName = "dateToString")
    PersonDTO conver(Person person);


4、MapStruct 集合映射、Map映射、枚举映射和Stream 流映射

4.1 集合映射

以上是关于MapStruct 一文读懂的主要内容,如果未能解决你的问题,请参考以下文章

龙华大道1号

正舵者科技分布式存储机房落地广东深圳

一文读懂FastQC Report

WGDBA|3月23日季军争夺赛首轮赛程预告深圳龙岗队VS广州队|2019广东省女子篮球联赛

一文读懂Python 高阶函数

广东省哈工大(深圳)赛区赛事活动安排