MapStruct的研究和使用

834 阅读8分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路

本文已参与「新人创作礼」活动,一起开启掘金创作之路

本文已参与「新人创作礼」活动,一起开启掘金创作之路

本文已参与「新人创作礼」活动,一起开启掘金创作之路

本文已参与「新人创作礼」活动,一起开启掘金创作之路

MapStruct的官网网址:

mapstruct.org

\

研究目的

  1. 看这个是不是bean 拷备属性的类库, 研究下怎么用,写点代码样例,文档出来.

  2. 对比下 其与其它bean拷备方式 有什么优缺点.  如apache的 BeanUtil , hutool的, spring的.

  3. idea 是不是也有一个这样的插件, 看这个插件又是做什么的?

\

idea的mapstruct插件:

\

1.mapstruct的优点:

    mapstruct是在代码编译的时候,生成其映射规则的代码,所以会在编译时暴露映射错误的代码,让错误提前暴露。因为使用的是一些简单方法:set/get或者Fluent方式,而非反射方式,所以可以更快的执行完成。可以通过04章节查看各个方法效率上对比可以实现深拷贝,上面4种方法中只有mapstruct可以实现深拷贝。但是需要进行配置(使用@mapper(mappingControl=DeepClone.class)进行设置)。其他的都是浅拷贝。从而mapstruct实现修改新对象不会对老对象产生影响。类型更加安全可以进行自定义的映射。可以自定义各种方法,完成属性映射。例如:将两个属性数据合并成一个设置到目标属性上

2.mapstruct的缺点:

    必须添加一个接口或者抽象类,才能实现映射。其他的三种方法都不用写额外接口或者类。

\

\

1.那么我们就需要明白,什么是mapstruct?

对象与对象之间的互相转换,就需要有一个专门用来解决转换问题的工具,要是每一个字段都 get/set 会很麻烦。

MapStruct 只需要定义一个 Mapper 接口,MapStruct 就会自动实现这个映射接口,避免了复杂繁琐的映射实现。

简单来说,mapstruct就是一个属性映射工具。

如果还是不懂,说白了就是方便了get/set方法使用。

\

2.下面用代码对比一下使用mapstruct前后

未使用mapstruct:

DTO:

CarDTO

package com.example.mapstruct.beans.dto;

\


import lombok.Data;

import java.util.Date;

import java.util.List;

\


@Data

public class CarDTO {

    /**

     * 汽车id

     */

    private Long id;

    /**

     * 车辆的编号

     */

    private String vin;

    /**

     * 车的价格

     */

    private double price;

    /**

     * 上路的价格

     */

    private double totalPrice;

    /**

     * 生产日期

     */

    private Date publishDate;

    /**

     * 牌子名字

     */

    private String brand;

    /**

     * 汽车的零件列表

     */

    private List<PartDTO> partDTOS;

    /**

     * 汽车的司机

     */

    private DriverDTO driverDTO;

}

\

DriverDTO:

package com.example.mapstruct.beans.dto;

\


import lombok.Data;

\


@Data

public class DriverDTO {

    /**

     * id

     */

    private Long id;

    /**

     *  驾驶员的名字

     */

    private String name;

}

\


PartDTO:

package com.example.mapstruct.beans.dto;

\


import lombok.Data;

\


@Data

public class PartDTO {

    /**

     * 汽车零件的id

     */

    private Long partId;

    /**

     * 零件的名字

     */

    private String partName;

}

VO:

CarVO:

package com.example.mapstruct.beans.vo;

\


import lombok.Data;

\


@Data

public class CarVO {

    /**

     * 汽车id

     */

    private Long id;

    /**

     * 车辆的编号

     */

    private String vin;

    /**

     * 车的价格

     */

    private Double price;

    /**

     * 上路的价格

     */

    private String totalPrice;

    /**

     * 生产日期

     */

    private String publishDate;

    /**

     * 牌子名字

     */

    private String brandName;

    /**

     * 是否配置了汽车的零件

     */

    private Boolean hasPart;

    /**

     * 汽车的司机

     */

    private DriverVO dirverVO;

}

DriverVO:

package com.example.mapstruct.beans.vo;

\


\


import lombok.Data;

\


\


@Data

public class DriverVO {

    /**

     * 驾驶员id

     */

    private Long driverId;

    /**

     * 驾驶员的名字

     */

    private String fullName;

}

PartVO:

package com.example.mapstruct.beans.vo;

\


import lombok.Data;

\


@Data

public class PartVO {

    /**

     * 汽车零件的id

     */

    private Long partId;

    /**

     * 零件的名字

     */

    private String partName;

}

测试:

package com.example.mapstruct;



import com.example.mapstruct.beans.dto.CarDTO;

import com.example.mapstruct.beans.dto.DriverDTO;

import com.example.mapstruct.beans.dto.PartDTO;

import com.example.mapstruct.beans.vo.CarVO;

import com.example.mapstruct.beans.vo.DriverVO;

import org.junit.jupiter.api.Test;

import org.junit.runner.RunWith;

import org.springframework.boot.test.context.SpringBootTest;

import org.springframework.test.context.junit4.SpringRunner;

import java.text.DecimalFormat;

import java.text.SimpleDateFormat;

import java.util.ArrayList;

import java.util.Date;

import java.util.List;




@RunWith(SpringRunner.class)

@SpringBootTest(classes = MapstructApplication.class)

class MapstructApplicationTests {




    @Test

    public void test1(){

        //模拟业务构造出的CarDTO对象

        CarDTO carDTO = buildCarDTO();

        // 转化dto-vo

        CarVO carVO = new CarVO();

        carVO.setId(carDTO.getId());

        carVO.setVin( carDTO.getVin( ));

        carVO.setPrice(carDTO.getPrice());

        //装箱拆箱机制,不需要我们自己转化

        double totalPrice = carDTO.getTotalPrice( );

        DecimalFormat df = new DecimalFormat("#.00");

        String totalPriceStr = df.format(totalPrice);

        carVO.setTotalPrice(totalPriceStr);

        Date publishDate = carDTO.getPublishDate();

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        String publishDateFormat = sdf.format( publishDate) ;

        carVO.setPublishDate(publishDateFormat);

        carVO.setBrandName(carDTO.getBrand( ) );

        List<PartDTO> partDTOS = carDTO.getPartDTOS();

        boolean hasPart = partDTOS != null && ! partDTOS.isEmpty();

        carVO.setHasPart( hasPart);

        DriverVO driverVO = new DriverVO( ) ;

        DriverDTO driverDTO = carDTO.getDriverDTO();

        driverVO.setDriverId( driverDTO.getId());

        driverVO.setFullName( driverDTO.getName( ) );

        carVO.setDirverVO(driverVO);

        System.out.println(carVO);

    }

\


\


    private CarDTO buildCarDTO(){

        CarDTO CarDTO = new CarDTO();

        CarDTO.setId(100L);

        CarDTO.setVin("辽B");

        CarDTO.setPrice(100.55);

        CarDTO.setPrice (123789.126d) ;

        CarDTO.setTotalPrice(143789.126d);

        CarDTO.setPublishDate(new Date());CarDTO.setBrand("大众");

        //零件

        PartDTO partDTO1 = new PartDTO();

        partDTO1.setPartId( 1L);

        partDTO1.setPartName("多功能方向盘");

        PartDTO partDTO2 = new PartDTO();

        partDTO2.setPartId( 2L);

        partDTO2.setPartName("智能车门");

        List<PartDTO> partDTOList = new ArrayList<>( );

        partDTOList.add(partDTO1);

        partDTOList.add(partDTO2);

        CarDTO.setPartDTOS(partDTOList);

        //设置驾驶员

        DriverDTO driverDTO = new DriverDTO();

        driverDTO.setId(88L);

        driverDTO.setName( "小明");

        CarDTO.setDriverDTO(driverDTO);

        return CarDTO;

    }

}

重点来了

3.使用mapstruct

3.1 导入maven依赖

不能用mapstruct-jdk8,否则会因为主动引入的依赖中没有mapstruct而导致排除springfox-swagger2中的mapstruct失败。主动引入改成mapstruct就行了,两者版本是同步的。

<dependency>

    <groupId>org.mapstruct</groupId>

    <artifactId>mapstruct</artifactId>

    <version>1.4.2.Final</version>

</dependency>

<dependency>

    <groupId>org.mapstruct</groupId>

    <artifactId>mapstruct-processor</artifactId>

    <version>1.4.2.Final</version>

</dependency>

4.mapstruct的使用

4.1不使用框架的缺点

  • 多而复杂的代码与业务逻辑耦合
  • 重复的劳动

4.2@Mapper

  • 默认映射规则
    • 同类型且同名的属性,会自动映射
    • mapstruct会自动进行类型转换
      • 8种基本类型和他们对应的包装类型之间
      • 8种基本类型(包括他们的包装类型)和string之间
      • 日期类型和string之间

4.3@Mappings和@Mapping

  • 指定属性之间的映射关系
    • 日期格式化:dataFormat = "yyyy-MM-dd HH:mm:ss"
    • 数字格式化
  • ignore
  • 属性是引用对象的映射处理
  • 批量映射

新建一个抽象类----CarConvert

package com.example.mapstruct.convert;

\


import com.example.mapstruct.beans.dto.CarDTO;

import com.example.mapstruct.beans.dto.DriverDTO;

import com.example.mapstruct.beans.dto.PartDTO;

import com.example.mapstruct.beans.vo.CarVO;

import com.example.mapstruct.beans.vo.DriverVO;

import com.example.mapstruct.beans.vo.PartVO;

import com.example.mapstruct.beans.vo.VehiclVo;

import org.mapstruct.*;

import org.mapstruct.factory.Mappers;

import java.util.List;



@Mapper(componentModel = "spring")

public abstract class CarConvert {

    public static CarConvert INSTANCE = Mappers.getMapper(CarConvert.class);

    @Mappings(

            //指定映射写法一

            value = {

            @Mapping(source = "totalPrice",target = "totalPrice",numberFormat = "#.00"),

            @Mapping(source = "publishDate",target = "publishDate",dateFormat = "yyyy-MM-dd HH:mm:ss"),

            @Mapping(source = "color",target = "color",ignore = true),

            @Mapping(source = "brand",target = "brandName"),

            @Mapping(source = "driverDTO",target = "dirverVO")

        }

    )



    public abstract CarVO dto2vo(CarDTO carDTO);


    //指定映射写法二

    @Mapping(source = "id",target = "driverId")

    @Mapping(source = "name",target = "fullName")

    public abstract DriverVO dto2vo2(DriverDTO driverDTO);


}

4.4@AfterMapping和@MappingTarget

  • 在映射最后一步对属性的自定义映射处理

//自定义映射处理

    @AfterMapping //表示让mapstruct在调用完自动转换的方法之后,会来自动调用本方法

    public void dto2vo3(CarDTO carDTO,@MappingTarget CarVO carVO){

        // @MappingTarget:表示传来的carVO对象已经赋值的

        List<PartDTO> partDTOS = carDTO.getPartDTOS();

        boolean hasPart = partDTOS != null && !partDTOS.isEmpty();

        carVO.setHasPart(hasPart);

    }

//批量映射处理

    public abstract List dto2vo4(List carDTO);

4.5@BeanMapping

  • @BeanMapping:配置忽略mapstruct的默认映射行为,只映射那些带@Mapping的属性
  • ignoreByDefault:避免不需要的赋值、避免属性覆盖

    //@BeanMapping:配置忽略mapstruct的默认映射行为,只映射带有Mapping的属性,防止不同类中的相同属性

    @BeanMapping(ignoreByDefault = true)

    @Mapping(target = "id")

    @Mapping(source = "brand",target = "brandName")

    public abstract VehiclVo dto2vo5(CarDTO carDTO);

4.6@InheritConfiguration

  • 更新的场景,避免同样的配置写多次

    @InheritConfiguration

    @Mapping(target = "id",ignore = true)

    public abstract void updto2vo(CarDTO carDTO,@MappingTarget VehiclVo vehiclVo);

4.7@InheritInverseConfiguration

  • 反向映射不用反过来再写一遍

注意:只继承@Mapping注解配置,不会继承@BeanMapping

    //@InheritInverseConfiguration:反向继承

    //name:指定使用哪一个方法的配置,然后写方法的名字就可以了

    @BeanMapping(ignoreByDefault = true)

    @InheritInverseConfiguration(name = "dto2vo5")

    public abstract CarDTO dto2vo6(VehiclVo vehiclVo);

\

\

4.8 mapstruct和spring结合

@Mapper(componentModel = "spring")

\

完整代码如下:

CarConvert:

package com.example.mapstruct.convert;

\


import com.example.mapstruct.beans.dto.CarDTO;

import com.example.mapstruct.beans.dto.DriverDTO;

import com.example.mapstruct.beans.dto.PartDTO;

import com.example.mapstruct.beans.vo.CarVO;

import com.example.mapstruct.beans.vo.DriverVO;

import com.example.mapstruct.beans.vo.PartVO;

import com.example.mapstruct.beans.vo.VehiclVo;

import org.mapstruct.*;

import org.mapstruct.factory.Mappers;

import java.util.List;

\


\


@Mapper(componentModel = "spring")

public abstract class CarConvert {

    public static CarConvert INSTANCE = Mappers.getMapper(CarConvert.class);

    @Mappings(

            //指定映射写法一

            value = {

            @Mapping(source = "totalPrice",target = "totalPrice",numberFormat = "#.00"),

            @Mapping(source = "publishDate",target = "publishDate",dateFormat = "yyyy-MM-dd HH:mm:ss"),

            @Mapping(source = "color",target = "color",ignore = true),

            @Mapping(source = "brand",target = "brandName"),

            @Mapping(source = "driverDTO",target = "dirverVO")

        }

    )

\


\


    public abstract CarVO dto2vo(CarDTO carDTO);

\


\


    //指定映射写法二

    @Mapping(source = "id",target = "driverId")

    @Mapping(source = "name",target = "fullName")

    public abstract DriverVO dto2vo2(DriverDTO driverDTO);

\


\


    //自定义映射处理

    @AfterMapping //表示让mapstruct在调用完自动转换的方法之后,会来自动调用本方法

    public void dto2vo3(CarDTO carDTO,@MappingTarget CarVO carVO){

        // @MappingTarget:表示传来的carVO对象已经赋值的

        List<PartDTO> partDTOS = carDTO.getPartDTOS();

        boolean hasPart = partDTOS != null && !partDTOS.isEmpty();

        carVO.setHasPart(hasPart);

    }

\


\


    //批量映射处理

    public abstract List<CarVO> dto2vo4(List<CarDTO> carDTO);

\


\


    //@BeanMapping:配置忽略mapstruct的默认映射行为,只映射带有Mapping的属性,防止不同类中的相同属性

    @BeanMapping(ignoreByDefault = true)

    @Mapping(target = "id")

    @Mapping(source = "brand",target = "brandName")

    public abstract VehiclVo dto2vo5(CarDTO carDTO);

\


\


    @InheritConfiguration

    @Mapping(target = "id",ignore = true)

    public abstract void updto2vo(CarDTO carDTO,@MappingTarget VehiclVo vehiclVo);

\


\


    //@InheritInverseConfiguration:反向继承

    //name:指定使用哪一个方法的配置,然后写方法的名字就可以了

    @BeanMapping(ignoreByDefault = true)

    @InheritInverseConfiguration(name = "dto2vo5")

    public abstract CarDTO dto2vo6(VehiclVo vehiclVo);

}

\


MapstructApplicationTests:

package com.example.mapstruct;

\


import com.example.mapstruct.beans.dto.CarDTO;

import com.example.mapstruct.beans.dto.DriverDTO;

import com.example.mapstruct.beans.dto.PartDTO;

import com.example.mapstruct.beans.vo.CarVO;

import com.example.mapstruct.beans.vo.DriverVO;

import com.example.mapstruct.beans.vo.VehiclVo;

import com.example.mapstruct.convert.CarConvert;

import org.junit.jupiter.api.Test;

import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowire;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.test.context.SpringBootTest;

import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;

import java.text.DecimalFormat;

import java.text.SimpleDateFormat;

import java.util.ArrayList;

import java.util.Date;

import java.util.List;

\


\


@RunWith(SpringRunner.class)

@SpringBootTest(classes = MapstructApplication.class)

class MapstructApplicationTests {

\


\


    //@Autowired

    @Resource

    private CarConvert carConvert;

\


\


\


\


    /**

     * test7:测试mapstruct与spring结合使用

     */

    @Test

    public void test7(){

        VehiclVo vehiclVo = new VehiclVo();

        vehiclVo.setId(200L);

        vehiclVo.setBrandName("宝马");

        vehiclVo.setPrice(500d);

        CarDTO carDTO = carConvert.INSTANCE.dto2vo6(vehiclVo);

        System.out.println(carDTO);

    }

\


\


    /**

     * test6:@InheritInverseConfiguration:反向继承

     */

    @Test

    public void test6(){

        VehiclVo vehiclVo = new VehiclVo();

        vehiclVo.setId(200L);

        vehiclVo.setBrandName("宝马");

        vehiclVo.setPrice(500d);

        CarDTO carDTO = CarConvert.INSTANCE.dto2vo6(vehiclVo);

        System.out.println(carDTO);

    }

\


\


    /**

     * test5:@InheritConfiguration

     */

    @Test

    public void test5(){

        CarDTO carDTO = buildCarDTO();

        VehiclVo vehiclVo = CarConvert.INSTANCE.dto2vo5(carDTO);

\


\


        CarDTO carDTO1 = new CarDTO();

        carDTO1.setBrand("奔驰");

        //通过carDTO2的属性值更新已经存在的vehicleVO对象

        CarConvert.INSTANCE.updto2vo(carDTO1,vehiclVo);

        System.out.println(vehiclVo);

    }

\


\


    /**

     * test4:BeanMapping

     */

    @Test

    public void test4(){

        CarDTO carDTO = buildCarDTO();

        VehiclVo vehiclVo = CarConvert.INSTANCE.dto2vo5(carDTO);

        System.out.println(vehiclVo);

    }

\


\


    /**

     * test3:使用mapstruct的批量转换list

     */

    @Test

    public void test3(){

        CarDTO carDTO = buildCarDTO();

        List<CarDTO> dto2volist = new ArrayList<>();

        dto2volist.add(carDTO);

        List<CarVO> dto2volist2 = CarConvert.INSTANCE.dto2vo4(dto2volist);

        System.out.println(dto2volist2);

    }

    /**

     * test2:使用mapstruct的单个转换

     */

    @Test

    public void test2(){

        CarDTO carDTO = buildCarDTO();

        CarVO carVO = CarConvert.INSTANCE.dto2vo(carDTO);

        System.out.println(carVO);

    }

\


\


    /**

     * test1:没有使用mapstruct

     */

    @Test

    public void test1(){

        //模拟业务构造出的CarDTO对象

        CarDTO carDTO = buildCarDTO();

        // 转化dto-vo

        CarVO carVO = new CarVO();

        carVO.setId(carDTO.getId());

        carVO.setVin( carDTO.getVin( ));

        carVO.setPrice(carDTO.getPrice());

        //装箱拆箱机制,不需要我们自己转化

        double totalPrice = carDTO.getTotalPrice( );

        DecimalFormat df = new DecimalFormat("#.00");

        String totalPriceStr = df.format(totalPrice);

        carVO.setTotalPrice(totalPriceStr);

        Date publishDate = carDTO.getPublishDate();

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        String publishDateFormat = sdf.format( publishDate) ;

        carVO.setPublishDate(publishDateFormat);

        carVO.setBrandName(carDTO.getBrand( ) );

        List<PartDTO> partDTOS = carDTO.getPartDTOS();

        boolean hasPart = partDTOS != null && ! partDTOS.isEmpty();

        carVO.setHasPart( hasPart);

        DriverVO driverVO = new DriverVO( ) ;

        DriverDTO driverDTO = carDTO.getDriverDTO();

        driverVO.setDriverId( driverDTO.getId());

        driverVO.setFullName( driverDTO.getName( ) );

        carVO.setDirverVO(driverVO);

        System.out.println(carVO);

    }

\


\


    private CarDTO buildCarDTO(){

        CarDTO CarDTO = new CarDTO();

        CarDTO.setId(100L);

        CarDTO.setVin("辽B");

        CarDTO.setPrice(100.55);

        CarDTO.setPrice (123789.126d) ;

        CarDTO.setTotalPrice(143789.126d);

        CarDTO.setPublishDate(new Date());

        CarDTO.setBrand("大众");

        CarDTO.setColor("粉色");

        //零件

        PartDTO partDTO1 = new PartDTO();

        partDTO1.setPartId( 1L);

        partDTO1.setPartName("多功能方向盘");

        PartDTO partDTO2 = new PartDTO();

        partDTO2.setPartId( 2L);

        partDTO2.setPartName("智能车门");

        List<PartDTO> partDTOList = new ArrayList<>( );

        partDTOList.add(partDTO1);

        partDTOList.add(partDTO2);

        CarDTO.setPartDTOS(partDTOList);

        //设置驾驶员

        DriverDTO driverDTO = new DriverDTO();

        driverDTO.setId(88L);

        driverDTO.setName( "小明");

        CarDTO.setDriverDTO(driverDTO);

        return CarDTO;



    }

}

总结如下:(一定要看,结合上面的代码)

都用大白话去理解

1.source:指定DTO的属性名字,target:指定VO的属性名字

这里注意点:

  1. 如果DTO和VO的属性名字相同,可以不写,Mapping直接可以映射
  2. 如果DTO和VO的属性名字相同,但是你还想要按照你的规则来映射,只写target="属性名"即可

关键字说明:

  1. numberFormat:转换后小数点保留后两位
  2. dateFormat:转换后的日期规定格式
  3. ignore:如果你不想映射该属性,设置ignore=true,转换后的结果为null

**
**

2.注解说明

  1. @AfterMapping:表示让mapstruct在调用完自动转换的方法之后,会来自动调用本方法
  2. @MappingTarget:表示传来的carVO对象已经赋值的
  3. @BeanMapping:配置忽略mapstruct的默认映射行为,只映射带有Mapping的属性,防止不同类中的相同属性
  4. @InheritInverseConfiguration:反向继承
    • name:指定使用哪一个方法的配置,然后写方法的名字就可以了