Java VO转PO(MapStruct使用)

一、代码分层介绍

1.应用分层与领域模型

阿里巴巴出的的《阿里巴巴Java开发手册》里面制定了一个工程规约,第一条就是关于应用分层的,如下:
在这里插入图片描述
按这个手册上说的,一般分为如下几层:

  • 开放接口层

  • 终端显示层

  • Web层

  • Service层

  • Manager层

  • Dao层

  • 外部接口或第三方平台
    在实际开发工作中比较常用的分层是Web层(Controller)、Service层、Dao层(Mapper)等3层。
    每一层的数据对象都放在各自的领域模型对象中,按照手册里面的分层领域模型规约,分为这几种

  • DO (Data Object):与数据库表结构一一对应,通过DAO层向上传输数据源对象。

  • DTO(Data Transfer Object):数据传输对象,Service和Manager向外传输的对象。

  • BO(Business Object):业务对象。可以由Service层输出的封装业务逻辑的对象。

  • QUERY:数据查询对象,各层接收上层的查询请求。注:超过2个参数的查询封装,禁止使用Map类来传输。

  • VO(View Object):显示层对象,通常是Web向模板引擎层传输的对象。

除了手册里面的 还有PO持久化对象和POJO普通Java对象。
在实际项目开发中, 没必要弄这么多种区分,一般常用是VO/DTO和PO/Entity这2种。通常保证业务逻辑层Service和数据库DAO层的操作对象严格划分出来,确保互相不渗透,不混用就行。也就是说:Controller层的数据对象不要直接渗透到DAO层,同理数据表实体对象Entity/PO也不要直接传到Controller层进行输出或展示。

在开发中Web(Controller)层调用Service层,然后Service层再调用Dao层,上层调用下层涉及到了层与层之间领域对象的转换。

2.为什么要应用分层开发和区分领域模型

  • 解耦,各层的职责分明,面向接口开发、易于扩展和维护。
  • 一些不需要的字段没必要传输到前端
  • 一些字段需要转换后才能在前端展示或者存储
  • 一些前端展示的字段不需要保存到数据库

3.不同的实体类间进行转换

一般有如下几种方式:

  • 手动设值拷贝,代码中充斥大量Set 和Get方法
  • 使用BeanUtil工具的copyProperties等方法拷贝对象属性
  • 使用Dozer:一个对象转换工具
  • 使用MapStruct:是一个代码生成器,它基于约定优于配置的方法,极大地简化了Java bean类型之间映射的实现。

本文要介绍的是MapStruct的使用,MapStruct生成的映射代码使用的是普通的方法调用,因此速度快、类型安全且易于理解。

官方地址:https://mapstruct.org/

二、使用MapStruct

1.官方文档Introduction翻译

MapStruct是一个Java注解处理器,用于生成类型安全的bean映射类。

您所要做的就是定义一个mapper接口,该接口声明任何必需的映射方法。
在编译过程中,MapStruct将生成此接口的实现类。这个实现类使用普通的Java方法调用来映射源对象和目标对象,即没有使用反射或类似的的技术。

与手工编写映射代码相比,MapStruct通过生成冗长且容易出错的代码来节省时间。遵循约定优于配置的方法,MapStruct使用合理的默认值,但在配置或实现特殊行为时,它会跳出你的方式。
与动态映射框架相比,MapStruct提供了以下优势:

  • 通过使用普通方法调用而不是反射来快速执行
  • 编译时类型安全,只有对象和属性之间的映射可以被映射,没有意外的映射一个订单实体到一个客户的DTO等。
  • 在构建时清除错误报告,比如:映射不完整(不是所有的目标属性都映射了) 或者 映射不正确(找不到正确的映射方法或类型转换)

2.添加MapStruct依赖

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-jdk8</artifactId>
    <version>1.3.1.Final</version>
</dependency>

<build>
<plugins>
  <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>1.3.1.Final</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
  </plugin>
</plugins>
</build>

注意事项:
1.确保您的项目使用的是Java 1.8或更高版本
2.Maven插件版本要3.6以上。
3.如果要结合Lombok一起使用,则Lombok版本要1.16.16版本以上,并且配置改成如下:

    <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <annotationProcessorPaths>

                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>1.3.1.Final</version>
                        </path>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>1.18.6</version>
                        </path>

                    </annotationProcessorPaths>
                </configuration>
            </plugin>

3.定义一个映射器接口

(1).基本映射

@Mapper
public interface EmpConvert {
    /**
     * @param emp
     * @return
     */
    @Mapping(source = "empname",target = "ename")
    EmpVo empToEmpVo(Emp emp);
}
  • 当属性与其目标实体对应项具有相同的名称时,将隐式映射该属性。
  • 当属性在目标实体中具有不同的名称时,可以通过@Mapping注解指定其名称
    MapperStruct根据定义的接口生成的实现类如下:
public class EmpConvertImpl implements EmpConvert {
    public EmpConvertImpl() {
    }

    public EmpVo empToEmpVo(Emp emp) {
        if (emp == null) {
            return null;
        } else {
            EmpVo empVo = new EmpVo();
            empVo.setEname(emp.getEmpname());
            empVo.setEmpno(emp.getEmpno());
            empVo.setJob(emp.getJob());
            empVo.setMgr(emp.getMgr());
            empVo.setHiredate(emp.getHiredate());
            empVo.setSal(emp.getSal());
            empVo.setComm(emp.getComm());
            empVo.setDeptno(emp.getDeptno());
            empVo.setDname(emp.getDname());
            empVo.setCreateTime(emp.getCreateTime());
            empVo.setUpdateTime(emp.getUpdateTime());
            return empVo;
        }
    }
}

MapStruct的一般原理是生成尽可能多的代码,就好像是你自己写的一样。特别是,这意味着通过普通的getter/setter调用而不是反射或类似的方法将值从源复制到目标。

(2).在映射器中添加默认方法

假设从A映射到B需要一些特殊的逻辑,需要特定的条件才能进行映射。一种方法是在B上实现一个自定义方法,然后由MapStruct生成的映射器调用这个自定义方法。
还有一种方法就是使用Java8的默认方法,在默认方法中手写映射逻辑。

@Mapper
public interface EmpConvert {
    /**
     * @param emp
     * @return
     */
    @Mapping(source = "empname",target = "ename")
    EmpVo empToEmpVo(Emp emp);

    /**
     * 使用默认方法实现特殊的映射逻辑
     * @param sysuser
     * @return
     */
    default SysuserVo  sysUserToSysuserVo(Sysuser sysuser){

    }

映射器也可以用抽象类的形式定义,而不是接口的形式,并直接在映射器类中实现自定义方法。在这种情况下,MapStruct将生成抽象类的扩展,实现所有的抽象方法。与声明默认方法相比,此方法的一个优点是可以在mapper类中声明其他字段。

(3).具有多个源参数的映射方法

MapStruct还支持带有多个源参数的映射方法。例如为了将多个实体合并成一个视图对象。

     /**
     * 在使用@Mapping注释时,必须指定属性所在的参数。
     * @param emp
     * @param dept
     * @return
     */
   @Mapping(source = "emp.empname", target = "ename")
    @Mapping(source = "emp.remark",target = "remark")
    @Mapping(source = "emp.deptno",target = "deptno")
    @Mapping(source = "dept.dname", target = "empDeptName")
    EmpVo empAndDeptToEmpVo(Emp emp, Dept dept);

上面这个方法接受两个源参数并返回一个组合的目标对象。与单参数映射方法一样,属性是按名称映射的。
如果多个源对象定义了具有相同名称的属性,则必须使用@Mapping注解指定用于检索属性的源参数,如示例中的deptno属性所示。当这样的歧义没有解决时,将会引发错误。对于在给定源对象中只存在一次的属性,可以选择指定源参数的名称,因为它可以自动确定。

如果所有源参数都为空,则带有多个源参数的映射方法将返回null。否则,将实例化目标对象,并传播所提供参数的所有属性。

MapStruct还提供了直接引用源参数的可能性,例如,源参数不是bean类型

  @Mapping(source = "emp.empname", target = "ename")
    @Mapping(source = "dname", target = "empDeptName")
    EmpVo fromEmp(Emp emp, String dname);

在本例中,源参数被直接映射到目标中,如上例所示。参数dname是一个非bean类型(在本例中是java.lang.String),它被映射到dname属性字段上。

(4).更新已存在的bean实例

有时候不需要创建目标类型的新实例,而是希望更新该类型的现有实例。这种映射可以通过为目标对象添加一个参数并使用@MappingTarget标记这个参数来实现

 /**
     * 更新已有的emp对象
     * @param empVo
     * @param emp
     */
    void updateEmpFromVo(EmpVo empVo,@MappingTarget Emp emp);

(5).公共字段映射

MapStruct还支持没有getter /setter的公共字段的映射。如果MapStruct不能找到适合属性的getter/setter方法,它将使用字段作为读/写访问器。

如果字段是public或public final,则将其视为读访问器。如果字段是静态的,则不将其视为读访问器。

只有在字段是公共的情况下,才将其视为写访问器。如果字段是final和/或静态的,则不认为它是写访问器。

4.调用映射器

1.通过映射器工厂调用

        Emp emp = empService.getByEmpno("592");
        EmpConvert empConvert = Mappers.getMapper(EmpConvert.class);
        EmpVo empVo = empConvert.empToEmpVo(emp);

按照惯例,mapper接口应该定义一个名为INSTANCE的成员,该成员持有mapper类型的单个实例

@Mapper
public interface EmpConvert {

    EmpConvert INSTANCE = Mappers.getMapper(EmpConvert.class);
    
    /**
     * @param emp
     * @return
     */
    @Mapping(source = "empname", target = "ename")
    EmpVo empToEmpVo(Emp emp);

这种模式使客户端很容易使用mapper对象,而无需重复实例化新实例:

EmpVo empVo = EmpConvert.INSTANCE.empToEmpVo(emp);

注意,MapStruct生成的映射器是无状态的,并且是线程安全的,因此可以同时从多个线程安全地访问。

2.使用依赖注入调用

如果您使用的是依赖项注入框架,比如CDI 或Spring框架,那么建议您通过依赖项注入来获取mapper对象,而不是通过上面描述的Mappers类

@Mapper(componentModel = "spring")
public interface EmpConvert {
    /**
     * @param emp
     * @return
     */
    @Mapping(source = "empname", target = "ename")
    EmpVo empToEmpVo(Emp emp);
    /**
     * 注入emp映射器
     */
    @Resource
    private EmpConvert empConvert;
    
    public EmpVo getEmp() {
        Emp emp = empService.getByEmpno("592");
        return empConvert.empToEmpVo(emp);
    }

5.数据类型转换

5.1隐式类型转换

在许多情况下,MapStruct会自动处理类型转换。例如,如果一个属性字段在源bean中是int类型的,而在目标bean中是String类型的,那么生成的映射器代码将通过分别调用字符串#valueOf(int)和整数#parseInt(String)来透明地执行转换。
会自动进行类型转换的有以下几种情况:

  • 所有Java原始数据类型及其对应的包装器类型之间的关系,例如int与Integer、boolean与Boolean等。生成的代码是空感知的,即当将包装器类型转换为相应的原语类型时,将执行空检查。
  • 在所有Java基础数字类型和包装器类型之间,例如在int和long或byte和Integer之间。
  • 所有Java基元类型(包括它们的包装器)和字符串之间的过渡,例如在int和String之间,或者在Boolean和String之间。由java.text理解的格式字符串。可以指定DecimalFormat。
  • 在枚举类型和字符串之间。
  • 在大数字类型之间(java.math。BigInteger、Java .math. bigdecimal)和Java基本类型(包括它们的包装器)以及字符串。由java.text理解的格式字符串。可以指定DecimalFormat。
  • java.time之间。LocalDateTime来自Java 8日期-时间包和Java .util.Date
  • 在java.sql.Timestamp 和java.util.Date之间
  • …其他的情况看官网

5.2映射对象引用

通常,一个对象不仅具有基本属性,而且还引用其他对象。例如,Emp类可以包含一个对Dept对象的引用,这个引用应该被EmpVo类映射到一个DeptVo对象。

public class Emp  {
    /**
     * 员工编号
     */
    private Long empno;
    /**
     * 所在部门对象
     */
    private Dept dept;
    ......
public class EmpVo {
    /**
     * 员工编号
     */
    private Long empno;
    /**
    */
     private DeptVo deptVo;
     ......

emp映射器如下:

@Mapper(componentModel = "spring")
public interface EmpConvert {
    EmpConvert INSTANCE = Mappers.getMapper(EmpConvert.class);

    /**
     * @param emp
     * @return
     */
    @Mapping(source = "empname", target = "ename")
    @Mapping(source = "dept",target = "deptVo")
    EmpVo empToEmpVo(Emp emp);
    
    DeptVo deptToDeptVo(Dept dept);

emp映射器生成的代码如下:

@Component
public class EmpConvertImpl implements EmpConvert {
    public EmpConvertImpl() {
    }

    public EmpVo empToEmpVo(Emp emp) {
        if (emp == null) {
            return null;
        } else {
            EmpVo empVo = new EmpVo();
            empVo.setEname(emp.getEmpname());
            empVo.setDeptVo(this.deptToDeptVo(emp.getDept()));
            empVo.setEmpno(emp.getEmpno());
            empVo.setJob(emp.getJob());
            empVo.setMgr(emp.getMgr());
            empVo.setHiredate(emp.getHiredate());
            empVo.setSal(emp.getSal());
            empVo.setComm(emp.getComm());
            empVo.setDeptno(emp.getDeptno());
            empVo.setCreateTime(emp.getCreateTime());
            empVo.setUpdateTime(emp.getUpdateTime());
            empVo.setRemark(emp.getRemark());
            return empVo;
        }
    }

    public DeptVo deptToDeptVo(Dept dept) {
        if (dept == null) {
            return null;
        } else {
            DeptVo deptVo = new DeptVo();
            deptVo.setDeptno(dept.getDeptno());
            deptVo.setDname(dept.getDname());
            deptVo.setLoc(dept.getLoc());
            deptVo.setRemark(dept.getRemark());
            return deptVo;
        }
    }

这样就可以映射任意深度对象层次。
在生成映射方法的实现时,MapStruct将对源和目标对象中的每个属性将遵从以下规则:

  • 如果源和目标属性具有相同的类型,则值将简单地从源复制到目标。如果属性是一个集合(例如一个列表),集合的副本将被设置到目标属性中
  • 如果源属性类型与目标属性类型不同,请检查是否存在另一种映射方法,该方法将源属性的类型作为参数类型,将目标属性的类型作为返回类型。如果存在这样一个方法,它将在生成的映射实现中被调用。
  • 如果不存在这样的方法,MapStruct将查看是否存在针对属性的源和目标类型的内置转换。如果是这种情况,生成的映射代码将应用这种转换。
  • 如果没有找到这样的方法,MapStruct将尝试生成一个自动的子映射方法,它将在源和目标属性之间进行映射。为了阻止MapStruct生成自动子映射方法,可以使用@Mapper(disableSubMappingMethodsGeneration = true)。
  • 如果MapStruct不能创建基于名称的映射方法,则在构建时将引发一个错误,指示不可映射的属性及其路径。

5.3控制嵌套bean映射

   /**
     * @param emp
     * @return
     */
    @Mapping(source = "empname", target = "ename")
    @Mapping(target = "deptVo",ignore = true)
    EmpVo empToEmpVo(Emp emp);

gnore = true 设置忽略deptVo属性

当从实体映射到视图对象时,在某一点上减少对其他实体的引用通常是有用的。为此,实现一个自定义映射方法,例如将引用的dept实体映射到目标对象中的empDeptName和deptno上,

public class Emp  {
    /**
     * 员工编号
     */
    private Long empno;
    /**
     * 所在部门对象
     */
    private Dept dept;
    ......
public class EmpVo {
    /**
     * 员工编号
     */
    private Long empno;
     /**
     * 所在部门编号
     */
    private int deptno;
 
    /**
     * 部门名称
     */
    private String empDeptName;
    ...

映射器代码如下:

    /**
     * @param emp
     * @return
     */
    @Mapping(source = "empname", target = "ename")
    @Mapping(source = "dept.dname",target = "empDeptName")
    @Mapping(source="dept.deptno",target = "deptno")
    EmpVo empToEmpVo(Emp emp);

生成的映射器实现类如下:

@Component
public class EmpConvertImpl implements EmpConvert {
    public EmpConvertImpl() {
    }

    public EmpVo empToEmpVo(Emp emp) {
        if (emp == null) {
            return null;
        } else {
            EmpVo empVo = new EmpVo();
            empVo.setEname(emp.getEmpname());
            empVo.setEmpDeptName(this.empDeptDname(emp));
            Integer deptno = this.empDeptDeptno(emp);
            if (deptno != null) {
                empVo.setDeptno(deptno);
            }

            empVo.setEmpno(emp.getEmpno());
            empVo.setJob(emp.getJob());
            empVo.setMgr(emp.getMgr());
            empVo.setHiredate(emp.getHiredate());
            empVo.setSal(emp.getSal());
            empVo.setComm(emp.getComm());
            empVo.setCreateTime(emp.getCreateTime());
            empVo.setUpdateTime(emp.getUpdateTime());
            empVo.setRemark(emp.getRemark());
            return empVo;
        }
    }
  private String empDeptDname(Emp emp) {
        if (emp == null) {
            return null;
        } else {
            Dept dept = emp.getDept();
            if (dept == null) {
                return null;
            } else {
                String dname = dept.getDname();
                return dname == null ? null : dname;
            }
        }
    }

    private Integer empDeptDeptno(Emp emp) {
        if (emp == null) {
            return null;
        } else {
            Dept dept = emp.getDept();
            if (dept == null) {
                return null;
            } else {
                Integer deptno = dept.getDeptno();
                return deptno == null ? null : deptno;
            }
        }
    }

5.4调用其他映射器

除了在同一mapper类型上定义的方法之外,MapStruct还可以调用在其他类中定义的映射方法,无论是由MapStruct生成的映射器,还是手工编写的映射方法。这对于在多个类中构造映射代码(例如,每个应用程序模块使用一个映射器类型)或者提供不能由MapStruct生成的自定义映射逻辑是很有用的。

例如,Emp类可能包含一个属性updateTime,类型是Timestamp,而相应的Vo属性的类型是String。为了映射这个属性,你可以像这样实现一个自定义转换类:

public class TimestampConvert {

    public String timeStampToString(Timestamp updateTime){
        return updateTime.toString();
    }
}

在EmpConvert接口的@Mapper注释中,引用了这样的TimestampConvert 类:

@Mapper(uses = TimestampConvert.class)
public interface EmpConvert {

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

    /**
     * @param emp
     * @return
     */
    @Mapping(source = "empname", target = "ename")
    @Mapping(source = "dept.dname",target = "empDeptName")
    @Mapping(source="dept.deptno",target = "deptno")
    EmpVo empToEmpVo(Emp emp);

    /**
     *
     * @param dept
     * @return
     */
    DeptVo deptToDeptVo(Dept dept);

在EmpConvert 接口的实现类中,MapStruct将查找一个将Timestamp对象映射到字符串的方法,在TimestampConvert类中找到它,并生成一个timeStampToString()调用来映射updateTime属性。
EmpConvert 接口实现类如下:

public class EmpConvertImpl implements EmpConvert {

    private final TimestampConvert timestampConvert = new TimestampConvert();

    @Override
    public EmpVo empToEmpVo(Emp emp) {
        if ( emp == null ) {
            return null;
        }

        EmpVo empVo = new EmpVo();

        empVo.setEname( emp.getEmpname() );
        empVo.setEmpDeptName( empDeptDname( emp ) );
        Integer deptno = empDeptDeptno( emp );
        if ( deptno != null ) {
            empVo.setDeptno( deptno );
        }
        empVo.setEmpno( emp.getEmpno() );
        empVo.setJob( emp.getJob() );
        empVo.setMgr( emp.getMgr() );
        empVo.setHiredate( emp.getHiredate() );
        empVo.setSal( emp.getSal() );
        empVo.setComm( emp.getComm() );
        empVo.setCreateTime( emp.getCreateTime() );
        empVo.setUpdateTime( timestampConvert.timeStampToString( emp.getUpdateTime() ) );
        empVo.setRemark( emp.getRemark() );

        return empVo;
    }

注意点:如果是采用依赖注入的方式来调用映射器,则当前映射器调用的其他映射器也要是可注入的

@Mapper(componentModel = "spring",uses = TimestampConvert.class)
public interface EmpConvert {
......
}
/**
 * 自定义时间转换器
 *
 * @author David Lin
 * @version: 1.0
 * @date 2020-04-12 17:24
 */
@Component
public class TimestampConvert {

    public String timeStampToString(Timestamp updateTime){
        return updateTime.toString();
    }
}

5.5将映射目标类型传递给自定义映射器

5.6将上下文或状态对象传递给自定义方法

5.7映射方法解析

在将属性从一种类型映射到另一种类型时,MapStruct查找将源类型映射到目标类型的最特定方法。该方法可以在相同的mapper接口上声明,也可以在另一个通过@Mapper#uses()注册的mapper上声明。这同样适用于工厂方法。

5.8基于限定符的映射方法选择

6.映射集合

集合类型(List、Set等)的映射与映射bean类型的映射方式相同,即在mapper接口中使用所需的源和目标类型定义映射方法。MapStruct支持来自Java集合框架的广泛的可迭代类型。

生成的代码将包含一个循环,该循环遍历源集合,转换每个元素并将其放入目标集合。如果在给定的映射器或其使用的映射器中找到集合元素类型的映射方法,则调用此方法来执行元素转换。或者,如果存在源和目标元素类型的隐式转换,则将调用此转换例程

@Mapper(componentModel = "spring")
public interface EmpConvert {
    /**
     * @param emp
     * @return
     */
    @Mapping(source = "empname", target = "ename")
    EmpVo empToEmpVo(Emp emp);

    /**
     * 
     * @param empList
     * @return
     */
    List<EmpVo> empsToEmpVos(List<Emp> empList);

7.自定义映射

有时需要在某些映射方法之前或之后应用自定义逻辑。MapStruct提供了两种方法:装饰器,它允许对特定映射方法进行类型安全的定制;映射前和映射后的生命周期方法,它允许对具有给定源或目标类型的映射方法进行通用定制。
(1).使用装饰器定制映射
在某些情况下,可能需要自定义一个生成的映射方法,例如,在目标对象中设置一个不能由生成的方法实现设置的附加属性。MapStruct使用装饰器支持这一需求。
要将装饰器应用到映射器类,请使用@DecoratedWith注释指定它。

装饰器必须是装饰的映射器类型的子类型。您可以使它成为一个抽象类,它只允许实现您想要自定义的mapper接口的那些方法。对于所有未实现的方法,将使用默认的生成例程生成对原始映射器的简单委托。
比如Emp转EmpVo类型时,想自定义job字段值,如下:

@Mapper(componentModel = "spring",uses = TimestampConvert.class)
@DecoratedWith(EmpDecorator.class)
public interface EmpConvert {

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

    /**
     * @param emp
     * @return
     */
    @Mapping(source = "empname", target = "ename")
    @Mapping(source = "dept.dname",target = "empDeptName")
    @Mapping(source="dept.deptno",target = "deptno")
    EmpVo empToEmpVo(Emp emp);
    /**
     *
     * @param dept
     * @return
     */
    DeptVo deptToDeptVo(Dept dept);
/**
 * emp类型转换装饰器
 *
 * @author David Lin
 * @version: 1.0
 * @date 2020-05-02 9:35
 */
public abstract class EmpDecorator implements  EmpConvert {

    @Resource
    private EmpConvert delegate;

    @Override
    public EmpVo empToEmpVo(Emp emp) {
        EmpVo empVo = delegate.empToEmpVo(emp);
        empVo.setJob("当前员工的工作岗位是:"+empVo.getEname());
        return empVo;
    }
}
@RequestMapping("/testEmp")
    public EmpVo testTime(){
        Emp emp = new Emp();
        emp.setEmpname("smith");
        emp.setEmpno(123L);
        emp.setJob("开发");

        Timestamp updateTime = new Timestamp(System.currentTimeMillis());
        emp.setUpdateTime(updateTime);

        EmpVo empVo =empConvert.empToEmpVo(emp);
        logger.info("the empVo updateTiem is {}",empVo.getUpdateTime());
        return empVo;
    }

(2).使用前映射和后映射方法进行定制映射
在定制映射器时,装饰器可能并不总是适合需要。您可以使用在映射开始之前或映射完成之后调用的回调方法。
回调方法可以在抽象映射器本身中实现,也可以在mapper #uses中的类型引用中实现,或者在用作@Context参数的类型中实现。
这里采用mapper#uses实现

**
 * emp映射方法增强
 *
 * @author David Lin
 * @version: 1.0
 * @date 2020-05-02 10:54
 */
 @Component
public class EmpAop {

    @BeforeMapping
    public void setEmpJob(Emp emp){
           emp.setJob("员工工作岗位:"+emp.getJob());
    }

    @AfterMapping
    public void setRemark(Emp emp, @MappingTarget EmpVo empVo){
        empVo.setRemark(emp.getEmpname()+":"+emp.getRemark());
    }

}
@Mapper(componentModel = "spring",uses = {TimestampConvert.class,EmpAop.class})
public interface EmpConvert {

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

    /**
     * @param emp
     * @return
     */
    @Mapping(source = "empname", target = "ename")
    @Mapping(source = "dept.dname",target = "empDeptName")
    @Mapping(source="dept.deptno",target = "deptno")
    EmpVo empToEmpVo(Emp emp);
    /**
     *
     * @param dept
     * @return
     */
    DeptVo deptToDeptVo(Dept dept);

如果@BeforeMapping / @AfterMapping方法有参数,那么只有当方法的返回类型(如果非void)可以赋值给映射方法的返回类型,并且所有参数都可以由映射方法的源或目标参数赋值时,才会生成方法调用 。

  • 使用@MappingTarget注释的参数由映射的目标实例填充。
  • 使用@TargetType注释的参数将使用映射的目标类型填充。
  • 使用@Context注释的参数由映射方法的上下文参数填充。
  • 任何其他参数都由映射的源参数填充。
    对于非空方法,如果映射方法不为空,则方法调用的返回值作为映射方法的结果返回。
    与映射方法一样,可以为前/后映射方法指定类型参数。

8.复用映射配置

9.高级映射选项

这里描述了几个高级选项,这些选项允许根据需要对生成的映射代码的行为进行微调。

1.默认值和常量

如果相应的源属性为空,可以指定默认值将预定义值设置为目标属性。在任何情况下都可以指定常量来设置这样的预定义值。默认值和常量被指定为字符串值。当目标类型是原语类型或已装箱类型时,将获取字符串值的文字值。位/八进制/十进制/十六进制模式在这种情况下是允许的,只要它们是有效的文字。在所有其他情况下,常量或默认值都要通过内置转换或调用其他映射方法进行类型转换

具有常量的映射不能包含对源属性的引用

@Mapper(componentModel = "spring",uses = {TimestampConvert.class,EmpAop.class})
public interface EmpConvert {

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

    /**
     * @param emp
     * @return
     */
    @Mapping(source = "empname", target = "ename")
    @Mapping(source = "dept.dname",target = "empDeptName",defaultValue = "开发部门")
    @Mapping(source="dept.deptno",target = "deptno")
    @Mapping(target = "updateTime",dateFormat = "yyyy-MM-dd",constant ="2020-05-02")
    EmpVo empToEmpVo(Emp emp);

生成的代码如下:

          if (this.empDeptDname(emp) != null) {
                empVo.setEmpDeptName(this.empDeptDname(emp));
            } else {
                empVo.setEmpDeptName("开发部门");
            }
  empVo.setUpdateTime("2020-05-02");

2.表达式

3.默认表达式

默认表达式是默认值和表达式的组合。它们只在源属性为空时使用。
同样的警告和限制也适用于默认表达式。只支持Java,而MapStruct不会在生成时验证表达式。

@Mapper(componentModel = "spring", imports = PlatStringUtil.class)
public interface EmpConvert {

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

    /**
     * @param emp
     * @return
     */
    @Mapping(source = "empname", target = "ename")
    @Mapping(source = "dept.dname", target = "empDeptName", defaultValue = "开发部门")
    @Mapping(source = "dept.deptno", target = "deptno")
    @Mapping(target = "updateTime", dateFormat = "yyyy-MM-dd", constant = "2020-05-02")
    @Mapping(source = "remark", target = "remark", defaultExpression = "java(PlatStringUtil.randomUUID())")
    EmpVo empToEmpVo(Emp emp);

上面这个例子演示了如何使用defaultExpression来设置一个remark字段(如果源字段为null),如果源对象设置了现有的remark,则可以使用它来获取它,如果没有设置,则创建一个新ID。请注意,指定了完全限定的包名,因为MapStruct不负责PlatStringUtil类的导入(除非在EmpConvert 中显式地使用它)。可以通过在@Mapper注释上定义导入来解决这个问题

4.确定结果类型

当结果类型具有继承关系时,选择映射方法(@Mapping)或工厂方法(@BeanMapping)可能会变得模糊。

5.控制“空”参数的映射结果

当映射方法的源参数等于null时,MapStruct提供对要创建的对象的控制。默认情况下将返回null。
但是,通过指定nullValueMappingStrategy = nullValueMappingStrategy。在@BeanMapping、@IterableMapping、@MapMapping或@Mapper或@MappingConfig上使用RETURN_DEFAULT,映射结果可以更改为返回空的默认值。
几种类型null值返回如下:

  • Bean mappings::一个“空”的目标bean将被返回,除了常量和表达式之外,它们将在出现时被填充。
  • Iterables / Arrays:将返回一个空的iterable。
  • Maps:将返回一个空map。

优先级:
在映射方法级别上设置nullValueMappingStrategy属性优先于@Mappe上设置, ,而@Mapper#nullValueMappingStrategy优先于@MappingConfig#nullValueMappingStrategy。

6.控制bean映射中“空”属性的映射结果(仅更新映射方法)

当源属性等于null或状态检查方法导致“缺席”时,MapStruct提供了对@MappingTarget带注释的目标bean中设置的属性的控制。
默认情况下,目标属性将设置为null。

  • 通过在@Mapping、@BeanMapping、@Mapper或@MappingConfig注解上,指定nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT,映射结果可以更改为返回默认值。对于List 集合对象,MapStruct生成一个ArrayList,对于Map集合对象,生成一个HashMap,对于数组,生成一个空数组,对于字符串生成一个“”,对于基本类型/基本类型的包装类型,生成一个false或0的表示。对于所有其他对象,将创建一个新实例。请注意,需要一个默认构造函数。如果不可用,则使用@Mapping#defaultValue。
  • 通过在 @Mapping, @BeanMapping, @Mapper 或者@MappingConfig注解上指定nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE,映射结果将等于带注释的@MappingTarget的原始值。

7.控制bean映射中“空”属性的检查结果

MapStruct提供了何时生成空检查的控制,默认情况下(nullValueCheckStrategy = NullValueCheckStrategy.ON_IMPLICIT_CONVERSION))会生成一个空检查:

当设置nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS时,如果源不是基本类型,则始终包含一个空检查,除非在源bean上定义了源状态检查器。

8.源存在检查

一些框架生成具有源状态检查器的bean属性。通常这是以hasXYZ方法的形式出现的,XYZ是bean映射方法中源bean上的一个属性。当MapStruct发现hasXYZ方法时,它将调用这个hasXYZ,而不是执行空检查。

9.异常

在调用映射方法时,调用应用程序可能需要处理异常。这些异常可以通过手工编写的逻辑和生成的内置映射方法或MapStruct的类型转换来抛出。当调用应用程序需要处理异常时,可以在映射方法中定义一个抛出子句:

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值