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 集合映射

基本集合属性映射转换

UserMapper 接口新增基本集合属性映射转换方法。

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;

import java.util.List;

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

    /**
     * 基本类型集合类型转换
     * @param list
     * @return
     */
    List<Integer> converCollBasic(List<String> list);
}

测试代码:

    public static void main(String[] args) {
        List<String> strs = Arrays.asList("1","2","3","4","5");
        List<Integer> ints = UserMapper.INSTANCT.converCollBasic(strs);
        ints.stream().forEach(i-> System.out.println(i));
    }

 测试结果:

1
2
3
4
5

复杂对象集合属性映射转换 

UserMapper 接口新增复杂对象集合属性映射转换方法。

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;

import java.util.List;

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

    /**
     * 基本类型集合类型转换
     * @param list
     * @return
     */
    List<Integer> converCollBasic(List<String> list);

    /**
     * 复杂类型集合类型转换
     * @param list
     * @return
     */
    List<UserDTO> converCollObject(List<User> list);
}

测试代码:

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

        User two = new User();
        two.setSysCreateDate("2022-10-21");
        two.setSysCreateId("2L");
        two.setName("周晨宇");
        two.setBirthday(format.parse("2021-07-03"));
        two.setPhone("110");

        List<User> container = new ArrayList<User>();
        container.add(one);
        container.add(two);

        List<UserDTO> dtos = UserMapper.INSTANCT.converCollObject(container);
        dtos.stream().forEach(item ->{
            System.out.println(item);
        });
    }

 测试结果

UserDTO(realname=周晨曦, telephone=119, birthday=Thu May 30 00:00:00 CST 2019)
UserDTO(realname=周晨宇, telephone=110, birthday=Sat Jul 03 00:00:00 CST 2021)

4.2 Map映射

Map的映射有专门的注解@MapMapping。其属性有keyDateFormat、valueDateFormat、keyNumberFormat、valueNumberFormat、keyQualifiedBy、valueQualifiedBy、keyQualifiedByName、valueQualifiedByName等,和@Mapping差不多。

UserMapper 接口新增Map属性映射转换方法。

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.MapMapping;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

import java.util.Date;
import java.util.List;
import java.util.Map;

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

    /**
     * 基本类型集合类型转换
     * @param list
     * @return
     */
    List<Integer> converCollBasic(List<String> list);

    /**
     * 复杂类型集合类型转换
     * @param list
     * @return
     */
    List<UserDTO> converCollObject(List<User> list);

    /**
     *Map 类型 转换
     * @param maps
     * @return
     */
    @MapMapping(valueDateFormat = "yyyy-MM-dd HH:mm")
    Map<String, String> converCollMap(Map<String, Date> maps);
}

测试代码:

    public static void main(String[] args) {
        HashMap<String, Date> sourceMap = new HashMap<String, Date>();

        sourceMap.put("1", new Date(System.currentTimeMillis() + 20360));
        sourceMap.put("2", new Date(System.currentTimeMillis() + 48100));
        sourceMap.put("3", new Date(System.currentTimeMillis() + 62900));

        Map<String, String> targetMap = UserMapper.INSTANCT.converCollMap(sourceMap);

        Iterator<Map.Entry<String, String>> entryIterator = targetMap.entrySet().iterator();
        Map.Entry<String, String> entry = null;
        while (entryIterator.hasNext()) {
            entry = entryIterator.next();
            System.out.println("key " + entry.getKey() + ",     value " + entry.getValue());
        }
    }

测试结果:

key 1,     value 2022-10-21 14:05
key 2,     value 2022-10-21 14:06
key 3,     value 2022-10-21 14:06

4.3 枚举映射

 MapStruct支持将一种Java枚举类型映射到另一种Java枚举类型的方法。默认情况下,源枚举中的每个常量都映射到目标枚举类型中具有相同名称的常量。如果需要,可以用注解@ValueMapping。将源枚举中的常量映射到具有其他名称的常量。来自源枚举的多个常量可以映射到目标类型中的相同常量。PS:@InheritInverseConfiguration@InheritConfiguration可以与结合使用@ValueMappings

默认情况下,如果源枚举类型的常量在目标类型中没有名称相同的对应常量,并且也未通过映射到另一个常量,则MapStruct将引发错误。这样可以确保以安全且可预测的方式映射所有常量。如果由于某种原因发生了无法识别的源值,则生成的映射方法将抛出IllegalStateException。

  MapStruct还具有一种将所有剩余(未指定)映射映射到默认映射的机制。在一组值映射中只能使用一次。它有两种风格:<ANY_REMAINING><ANY_UNMAPPED>。这两个在官方文档里面解释的比较模糊,我是一时没读懂,然后我用自己写的例子理解了,先看看这个例子吧。

package com.zzg.mapstruct.enums;

import lombok.Getter;
import lombok.Setter;

public enum OrderEnum1 {
    ORDER_A("A", "序号A"),
    ORDER_B("B", "序号B"),
    ORDER_C("C", "序号C"),
    ORDER_OTH("D", "其他序号");

    @Setter
    @Getter
    private String code;
    @Setter
    @Getter
    private String desc;

    OrderEnum1(String code, String desc) {
        this.code = code;
        this.desc = desc;
    }
}
package com.zzg.mapstruct.enums;

import lombok.Getter;
import lombok.Setter;
public enum OrderEnum2 {
    ORDER_1("1)", "序号1"),
    ORDER_2("2)", "序号2"),
    ORDER_B("B", "序号B"),
    ORDER_C("C", "序号C"),
    ORDER_5("5)", "序号5"),
    ORDER_6("6)", "其他序号");

    @Setter
    @Getter
    private String code;
    @Setter
    @Getter
    private String desc;

    OrderEnum2(String code, String desc) {
        this.code = code;
        this.desc = desc;
    }
}

 @Mapper 接口定义:

package com.zzg.mapstruct.mapper;

import com.zzg.mapstruct.enums.OrderEnum1;
import com.zzg.mapstruct.enums.OrderEnum2;
import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
import org.mapstruct.ValueMapping;
import org.mapstruct.ValueMappings;
import org.mapstruct.factory.Mappers;

@Mapper
public interface EnumMapper {
    EnumMapper INSTANCT = Mappers.getMapper(EnumMapper.class);

    @ValueMappings({
            @ValueMapping(source = MappingConstants.NULL, target = "ORDER_B"),
            @ValueMapping(source = "ORDER_1", target = "ORDER_A"),
            @ValueMapping(source = "ORDER_2", target = "ORDER_A"),
            @ValueMapping(source = MappingConstants.ANY_REMAINING, target = "ORDER_OTH")
    })
    OrderEnum1 toOrderEnum1(OrderEnum2 orderEnum2);
}

测试代码:

package com.zzg.mapstruct.mapper;

import com.zzg.mapstruct.enums.OrderEnum1;
import com.zzg.mapstruct.enums.OrderEnum2;
import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
import org.mapstruct.ValueMapping;
import org.mapstruct.ValueMappings;
import org.mapstruct.factory.Mappers;

@Mapper
public interface EnumMapper {
    EnumMapper INSTANCT = Mappers.getMapper(EnumMapper.class);

    @ValueMappings({
            @ValueMapping(source = MappingConstants.NULL, target = "ORDER_B"),
            @ValueMapping(source = "ORDER_1", target = "ORDER_A"),
            @ValueMapping(source = "ORDER_2", target = "ORDER_A"),
            @ValueMapping(source = MappingConstants.ANY_REMAINING, target = "ORDER_OTH")
    })
    OrderEnum1 toOrderEnum1(OrderEnum2 orderEnum2);
}

测试结果:

key 值:A
value 值:序号A

知识拓展: 在 上述的代码实例中,枚举转枚举在实际的开发使用的不是很多,但是基本类型转枚举对象的情况就比较常见。

实例:在User类中新增一个sex 属性,在UserDTO 中新增一个sexEnum 枚举对象。

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;

    private String sex;

}
package com.zzg.mapstruct.entity;

import com.zzg.mapstruct.enums.SexEnum;
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;
    private SexEnum sexEnum;

}

package com.zzg.mapstruct.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@AllArgsConstructor
@NoArgsConstructor
public enum SexEnum {
    Man("1", "男"),
    Woman("2","女");

    private static final Map<String, SexEnum> MAP = Stream.of(SexEnum.values()).collect(Collectors.toMap(SexEnum :: getCode, Function.identity()));
    @Setter
    @Getter
    public String code;

    @Setter
    @Getter
    public String desc;



    public static SexEnum getInstance(String code) {
        return MAP.get(code);
    }

    @Override
    public String toString() {
        StringBuffer buffer =new StringBuffer();
        buffer.append("{");
        buffer.append("'code':'".concat(this.code).concat("',"));
        buffer.append("'desc':'".concat(this.desc).concat("'"));
        buffer.append("}");

        return buffer.toString();
    }
}
@Mapper(config = CommonConfig.class, uses = EnumUtil.class)
public interface UserMapper {
    UserMapper INSTANCT = Mappers.getMapper(UserMapper.class);

    @Mapping(source = "name", target="realname")
    @Mapping(source = "phone", target="telephone")
    @Mapping(source = "sex", target="sexEnum")
    UserDTO converMapping(User user);
}
package com.zzg.mapstruct.util;

import com.zzg.mapstruct.enums.SexEnum;

public class EnumUtil {
    public SexEnum asEnum(String code) {
       return SexEnum.getInstance(code);
    }
}

测试代码:

    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");
        user.setSex("1");

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

 测试效果:

UserDTO(realname=周晨曦, telephone=119, birthday=Thu May 30 00:00:00 CST 2019, sexEnum={'code':'1','desc':'男'})
Fri Oct 21 00:00:00 CST 2022
1L
{'code':'1','desc':'男'}

4.4 Stream流映射

UserMapper 接口新增Stream流映射转换方法。

package com.zzg.mapstruct.mapper;


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

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

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

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

    /**
     * 基本类型集合类型转换
     * @param list
     * @return
     */
    List<Integer> converCollBasic(List<String> list);

    /**
     * 复杂类型集合类型转换
     * @param list
     * @return
     */
    List<UserDTO> converCollObject(List<User> list);

    /**
     *Map 类型 转换
     * @param maps
     * @return
     */
    @MapMapping(valueDateFormat = "yyyy-MM-dd HH:mm")
    Map<String, String> converCollMap(Map<String, Date> maps);

    /**
     * 流 类型转换
     * @param source
     * @return
     */
    Stream<String> converStream(Stream<Integer> source);
}

测试代码:

        public static void main(String[] args) {
                Stream<Integer> nums = Arrays.asList(1,2,3,4,5).stream();
                Stream<String> strs = UserMapper.INSTANCT.converStream(nums);
                strs.forEach(i -> System.out.println(i));
        }

 测试结果:

1
2
3
4
5

5、MapStruct 其他

5.1 MapStruct 异常处理

Mapstruct 映射器允许抛出特定的异常。 考虑一个自定义映射方法的情况,我们希望在出现无效数据的情况下抛出自定义异常。

@MapperConfig(unmappedTargetPolicy = ReportingPolicy.ERROR, uses = DateFormtUtil.class)
public interface CommonConfig {
    @Mapping(source = "sysCreateDate", target = "sysCreateDate")
    BaseDTO converBase(BasePO basePO) throws ParseException;
}
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) throws ParseException {

            return date != null ? new SimpleDateFormat( "yyyy-MM-dd" )
                    .parse( date ) : null;

    }
}

5.2 MapStruct 自定义映射

有时需要在某些映射方法之前或之后应用自定义逻辑。MapStruct提供了两种方法:装饰器允许对特定映射方法进行类型安全的自定义,而映射前和映射后生命周期方法则允许对具有给定源或目标类型的映射方法进行通用的自定义。

使用装饰器自定义映射

        装饰器用到了@DecoratedWith 这个注解,装饰器必须是装饰的映射器类型的子类型。可以定义为抽象类。该抽象类仅允许实现您要自定义的mapper接口的那些方法。对于所有未实现的方法,将使用默认生成例程生成对原始映射器的简单委托。

  我们看个例子。我们将person转化成了personDto例子。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {
    //主键
    private String id;
    //名称
    private String name;
    //省
    private String province;
    //市
    private String city;
    //生日
    private Date birthday;
    //电话
    private String phone;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public class PersonDto {
    //主键
    private String id;
    //名称
    private String name;
    //地址(省+市)
    private String address;
    //生日
    private Long birthday;
    //电话
    private String phone;
}

@Mapper
public interface PersonMapper {

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

    @Mappings({
            @Mapping(target = "address",expression = "java(person.getProvince()+'-'+person.getCity())"),
            @Mapping(target = "birthday",expression = "java(person.getBirthday().getTime())")
    })
    PersonDto toDto(Person person);
}

@Test
    public void test1(){
        Person person = new Person("001","张三","江苏省","南京市",new Date(),"17600987654");
        PersonDto personDto = PersonMapper.INSTANCE.toDto(person);
        System.out.println(personDto);
    }

        使用装饰者模式去增强——定义一个抽象类,实现映射器接口。目的是对phone进行模糊处理。这里相当于使用了一个代理,类似与Aop,我们可以在方法执行前,执行后,或者执行前后都对方法的执行进行增强。

//定义一个抽象类,实现映射器接口
public abstract class PersonMapperDecorator implements PersonMapper {

    private final PersonMapper delegate;

    public PersonMapperDecorator(PersonMapper delegate) {
        this.delegate = delegate;
    }

    @Override
    public PersonDto toDto(Person person) {
        PersonDto dto = delegate.toDto(person);

        //对电话号码进行加密
        String phone = dto.getPhone();
        if (StringUtils.isNotEmpty(phone) && phone.length() >= 11) {
            dto.setPhone(phone.substring(0, 3) + "*****" + phone.substring(7, phone.length() - 1));
        }
        return dto;
    }
}

  在映射器里面使用@DecorateWith引入。

@Mapper
@DecoratedWith(PersonMapperDecorator.class)
public interface PersonMapper {
    PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class);

    @Mappings({
            @Mapping(target = "address",expression = "java(person.getProvince()+'-'+person.getCity())"),
            @Mapping(target = "birthday",expression = "java(person.getBirthday().getTime())")
    })
    PersonDto toDto(Person person);
}

测试 代码及其结果:

@Test
public void test1(){
    Person person = new Person("001","张三","江苏省","南京市",new Date(),"17600987654");
    PersonDto personDto = PersonMapper.INSTANCE.toDto(person);
    System.out.println(personDto);
}

 @BeforeMapping与@AfterMapping自定义映射

待补充。

5.3 MapStruct Null值处理

源对象NULL映射

当映射的source对象为null时可以通过@BeanMapping@IterableMapping@MapMapping(优先级最高),@Mappe@MappingConfig(优先级最低)的nullValueMappingStrategy策略来控制NULL值的映射结果。

策略值有:NullValueMappingStrategy.RETURN_NULL(默认,源为null,目标直接返回null)、NullValueMappingStrategy.RETURN_DEFAULT(返回一个对象,除填充的常量和表达式,其他字段属性为空;集合返回一个对象,size为0)。
 

    @Mapping(source = "name", target="realname", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_NULL)
    @Mapping(source = "phone", target="telephone")
    @Mapping(source = "sex", target="sexEnum")
    UserDTO converMapping(User user);

 温馨提示:

  • 集合使用了NullValueMappingStrategy.RETURN_DEFAULT策略所以源为NULL时返回return new ArrayList<TestThreeBO>();bean对象使用默认策略所以源为NULL时返回return null。
  • bean对象使用NullValueMappingStrategy.RETURN_DEFAULT策略,并且知道常量和表达式。

在更新时源对象属性NULL映射

在使用@MappingTarget更新目标时,可以设置@Mapping、@BeanMapping(优先级最高),@Mapper或@MappingConfig(优先级最底)的nullValuePropertyMappingStrategy属性值,用于设置当源对象属性为null时,如何设置目标属性的值。nullValuePropertyMappingStrategy的值有:

NullValuePropertyMappingStrategy.SET_TO_DEFAULT:源属性为null,目标属性会被赋予特定的默认值。List被赋予ArrayList,Map被赋予HashMap,数组就是空数组,String是“”,基本类型或包装类是0或false,对象是空的构造方法。

NullValuePropertyMappingStrategy.IGNORE:源属性为null,忽略目标属性的设值。

NullValuePropertyMappingStrategy.SET_TO_NULL:默认,源属性是null,目标属性也是null。


@Mapper
public interface UserMapper {
 
    @Mapping(target = "name", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT)
    @Mapping(target = "price", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
    void conver(User user, @MappingTarget UserDTO userDTO);

何时NULL检查

在使用@MappingTarget更新目标时,可以设置@Mapping、@BeanMapping(优先级最高),@Mapper或@MappingConfig(优先级最底)的nullValuePropertyMappingStrategy属性值,设置何时对源属性值进行null检查。nullValuePropertyMappingStrategy可选:

ON_IMPLICIT_CONVERSION:默认;包装类赋予基本类型、直接setter对象类型、类型转换后进行setter。

ALWAYS:总是进行null值检查。

@Mapper
public interface UserMapper {
 
    @Mapping(target = "id", nullValueCheckStrategy = ALWAYS)
    UserDTO cover(User user);
 

6、MapStruct 常见注解总结

6.1、@Mapper——表示该接口作为映射接口,编译时MapStruct处理器的入口

  1)uese:外部引入的转换类;

  2)componentModel:就是依赖注入,类似于在spring的servie层用@servie注入,那么在其他地方可以使用@Autowired取到值。该属性可取的值为

    a)默认:这个就是经常使用的 xxxMapper.INSTANCE.xxx;

    b)cdi:使用该属性,则在其他地方可以使用@Inject取到值;

    c)spring:使用该属性,则在其他地方可以使用@Autowired取到值;

    d)jsr330/Singleton:使用者两个属性,可以再其他地方使用@Inject取到值;

6.2、@Mappings——一组映射关系,值为一个数组,元素为@Mapping

6.3、@Mapping——一对映射关系

  1)target:目标属性,赋值的过程是把“源属性”赋值给“目标属性”;

  2)source:源属性,赋值的过程是把“源属性”赋值给“目标属性”;

  3)dateFormat:用于源属性是Date,转化为String;

  4)numberFormat:用户数值类型与String类型之间的转化;

  5)constant:不管源属性,直接将“目标属性”置为常亮;

  6)expression:使用表达式进行属性之间的转化;

  7)ignore:忽略某个属性的赋值;

  8)qualifiedByName:根据自定义的方法进行赋值;

  9)defaultValue:默认值;

6.4、@MappingTarget——用在方法参数的前面。使用此注解,源对象同时也会作为目标对象,用于更新。

6.5、@InheritConfiguration——指定映射方法

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

6.7、@Named——定义类/方法的名称

  • 6
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值