Mapstruct中类型的映射规则(二)

        上一篇文章主要讲解了Mapstruct的介绍、Mapstruct的优缺点、MapStruct简单的映射示例以及和常见的映射工具做了性能上的对比。

        这篇文章主要讲解Mapstruct的属性之间的映射规则,只有了解了这些规则之后,你才能更好地理解mapstruct如何做映射,才能按照你的想法进行对象属性之间的映射。

在属性映射方面,mapstruct相比于其他映射工具有:

  • 可以进行不同属性名之间的映射,使用@Mapping注解的source和target
  • 可以使用表达式进行设置,使用@Mapping注解中的expression
  • 可以设置默认值,使用@Mapping注解中的constant,
  • 可以进行不同类型之间的映射,并且更加安全
  • 可以进行自定义映射方法

01

mapstruct的映射文档


1.可以参考mapstruct的doc的第五章了解完整的映射规则:这里

02

mapstruct的映射控制说明


2.1 映射控制的作用:

        在源对象目标对象之间进行映射时,哪种映射方法将被考虑。即:只有在有这个控制条件下,才会执行对应的映射方法。

        所有属性之间的复制或者拷贝都将是依据以下映射控制来完成的。只有更好地理解这四种映射控制,才能按照我们的想法进行对象之间的拷贝。

2.2 映射控制的种类:

mapstruct一共有四种映射控制

  • MappingControl.Use.DIRECT

        直接映射:直接将源属性赋值到目标属性上,如果是对象的话,就是指针复制,也就是浅拷贝。这种方法有一个前提:目标属性是源属性的父类或者相同类型

  • MappingControl.Use.BUILT_IN_CONVERSION

        内置映射:将使用mapstruct内置的映射方法。有哪些内置映射方法:具体可参考文档第五章中第一节中。例如:日期和字符串、原生类型和对应的包装对象、字符串和枚举类型等等。

  • MappingControl.Use.MAPPING_METHOD

        映射方法:包含两类:可以是自定义的映射方法,也可以是系统自动生成的映射方法。注意:自定义映射方法比系统自动生成的映射方法的优先级更高。例如:深度拷贝就是使用这个映射控制。系统自动生成的映射方法:一般都是对象之间的映射。

  • MappingControl.Use.COMPLEX_MAPPING

        复合映射:是一种结合BUILT_IN_CONVERSIONMAPPING_METHOD的映射控制来完成对象属性之间的拷贝。当进行COMPLEX_MAPPING时:一共有三种形态:

                a) target = method1( method2( source ) )

                b) target = method( conversion( source ) )

                c) target = conversion( method( source ) )

        其中method开头表示:自定义映射方法,conversion开头的表示:内置映射方法

        注意:复合映射中只能进行两次转化,不能多于两次,也不能少于两次。这三种形态不含两个内置映射方法来完成属性之间的拷贝。即:至少有一个自定义的映射方法的存在。

        注意:在没有某一中映射控制时,就不会采用这种方式完成属性映射,例如:没有MAPPING_METHOD就会不采用自定义的映射方法完成属性映射。

2.3 映射控组组合

        四种映射控制可以组合使用,即同时使用两个及以上的映射控制,从而在这些映射控制中选择一种方式使用。那这就涉及到映射控制之间优先级

2.4 mapstruct内置的映射控制组合

        在mapstruct中一共有三种内置的映射控制组合,以及自定义的映射组合。

  • @MappingControl注解类

            该注解使用了全部的映射控制:DIRECT、MAPPING_METHOD、 BUILT_IN_CONVERSION、COMPLEX_MAPPING。这也这是:默认使用映射控制。
  • @DeepClone注解类

           该注解只使用了:MAPPING_METHOD映射,当我们使用这个注解时,将不会采用其他的三种方式来完成属性之间的拷贝。
  • @NoComplexMapping注解类

           改注解使用了三种:DIRECT、MAPPING_METHOD、 BUILT_IN_CONVERSION,即将不会使用复合映射方法完成属性之间的拷贝
  • 自定义的映射组合

                我们可以声明一个注解类,自定义只是用哪些映射控制,例如:只使用内置的映射方法

package com.moxiao.mapstruct.annotation;

import org.mapstruct.control.MappingControl;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * @author moxiao
 */
@Retention(value = RetentionPolicy.CLASS)
@MappingControl( MappingControl.Use.BUILT_IN_CONVERSION )
public @interface BuildInConversionControl {
}

2.5 映射控制的使用

        mapstruct默认使用的是:@MappingControl注解类,即四种映射控制

        我们可以在@Mapper注解中的mappingControl属性中设置,他的属性值是一个注解类。在@Mapper注解中设置这个属性:将会应用到这个类中的所有方法的属性之间的拷贝

        我们也可以在@Mapping注解中的mappingControl属性中设置。在@Mapping注解中设置这个属性:将只会应用到指定属性上。

        @Mapping的优先级比@Mapper的高,即优先采用@Mapping,如果没有才会使用@Mapper进行属性之间的映射。

03

mapstruct的映射控制优先级


        在有多个映射控制使用时,例如在使用:@MappingControl注解,mapstruct将会如何进行属性之间的映射。这里就涉及到四种映射控制之间的优先级。

3.1 四种映射控制的优先级,从高到低

  1. 在有MAPPING_METHOD控制下,并且包含有一个自定义的映射方法。这个自定义方法必须满足:输入参数源属性父级或者相同类型,这个映射方法的返回值目标属性的子类或相同类型。必须都满足这两个条件:这样在进行属性拷贝时,才会使用自定义的映射方法。不然则跳过。
  2. 在有DIRECT控制下,并且目标属性必须是源属性的父类或者相同类型。功能:直接将源属性的指针拷贝到目标属性下。必须满足这两个条件,才会使用指针拷贝,不然则跳过。
  3. 如果有BUILT_IN_CONVERSION的控制下,在源属性和目标属性之间拷贝时,mapstruct就会根据源属性类型和目标属性类型找到对应的内置映射方法使用,没有找到,就跳过。
  4. 如果有COMPLEX_MAPPING控制下,它又划分成三种类型:

                 如果存在两个自定义的方法,可以将源属性转化为目标属性,即: target = method1( method2( source ) ),优先使用。

                如果有一个自定义方法,一个内置映射方法,可以将源属性转化为目标属性,就会使用其中一个target = method( conversion( source ) )或者target = conversion( method( source ) )。

     5. 如果有MAPPING_METHOD控制下,将自动生成一个映射方法,完成属性之间的映射。必须对象,属性不能是:8中基本类型和对应的包装类以及String

3.2 注意事项:

        如果源属性是8种基本类型或者包装体,而目标属性是对应的类型(不能是不对应的),则会直接拷贝。就算有就不会走上面的优先级。例如:源属性为long,目标属性为Long,不管配置的什么,都会直接赋值。

04

mapstruct的映射控制的示例


1.源对象

public class MappingOrder {

    private String name;

    private Integer age;

    private LocalDateTime birthTime;

    private MapperOrderChild mapperOrderChild;

    private MappingOrderChildChild mapperOrderChild2;
    //省略其set/get方法
 }

2.目标对象

public class MappingOrderVO {

    private String name;

    private Number age;

    private String birthString;

    private MapperOrderChild mapperOrderChild;

    private MapperOrderChild2 mapperOrderChild2;
 }

3.映射类(必须自己单独定义一个,mapstruct必须提供一个mapper接口或者抽象类)

package com.moxiao.mapstruct.mapper;

import com.moxiao.mapstruct.entity.MappingOrder;
import com.moxiao.mapstruct.entity.MappingOrderVO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.control.DeepClone;

/**
 * @author moxiao
 */
@Mapper
public interface MapperOrderMapper {

    @Mapping(source = "birthTime", target = "birthString", dateFormat = "yyyy-MM-dd HH:mm:ss")
    //这一行进行深度拷贝,注意最后一个参数很重要:mappingControl = DeepClone.class
    @Mapping(source = "mapperOrderChild2", target = "mapperOrderChild2", mappingControl = DeepClone.class)
    MappingOrderVO entityToVo(MappingOrder mappingOrder);
    
}

4.mapstruct自动生成的文件

public class MapperOrderMapperImpl implements MapperOrderMapper {

    @Override
    public MappingOrderVO entityToVo(MappingOrder mappingOrder) {
        if ( mappingOrder == null ) {
            return null;
        }

        MappingOrderVO mappingOrderVO = new MappingOrderVO();
        
        //第一个属性,因为没有自定义方法从LocalDateTime转化为String,将跳过,
        //不能直接映射,优先级第二条
        //mapstruct有一个从LocalDateTime转化为String的内置方法,从而满足第三条映射
        if ( mappingOrder.getBirthTime() != null ) {
            mappingOrderVO.setBirthString( DateTimeFormatter.ofPattern( "yyyy-MM-dd HH:mm:ss" ).format( mappingOrder.getBirthTime() ) );
        }
        
        //我们在方法中使用DEEPCLONE,即MAPPING_METHOD控制
        //所以只有第一条优先级和第五条优先级,满足。因为没有自定义映射方法,从而跳过
        //所以Mapstruct使用第五条,自动生成一个映射方法
        mappingOrderVO.setMapperOrderChild2( mappingOrderChildChildToMapperOrderChild2( mappingOrder.getMapperOrderChild2() ) );
        //DIRECT优先级获胜
        mappingOrderVO.setName( mappingOrder.getName() );
        //DIRECT优先级获胜
        mappingOrderVO.setAge( mappingOrder.getAge() );
        //DIRECT优先级获胜
        mappingOrderVO.setMapperOrderChild( mappingOrder.getMapperOrderChild() );
        return mappingOrderVO;
    }

    protected MapperOrderChild2 mappingOrderChildChildToMapperOrderChild2(MappingOrderChildChild mappingOrderChildChild) {
        if ( mappingOrderChildChild == null ) {
            return null;
        }
        MapperOrderChild2 mapperOrderChild2 = new MapperOrderChild2();

        mapperOrderChild2.setFirstName( mappingOrderChildChild.getFirstName() );
        mapperOrderChild2.setSecondName( mappingOrderChildChild.getSecondName() );

        return mapperOrderChild2;
    }
}

结论:最终生成的Java映射代码,会根据01章节中第5条的映射优先级,进行属性之间的映射。

更多精彩内容:请关注公众号:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值