mapStruct + lombok→自定义不同属性之间复制,强于beanUtils

本文介绍了如何结合MapStruct和Lombok进行深度定制的属性复制,包括不同名称和类型的属性映射,以及如何在Spring中集成MapStruct。文章详细讲解了注意事项、所需的Lombok和MapStruct版本,以及@Mapper、@Mapping等相关注解的用法,并通过多个测试案例展示了映射规则的实践应用。
  1. 参考视频 ------ 参考demo ------- mapStruct官方文档

  2. 概述:
    1).mapStruct和beanUtils一样,都是浅复制,具体参照本人深/浅复制文章
    2).mapStruct相对于beanUtils,可以复制更为复杂的属性,不同类型,不同名之间也可以映射,以及可以实现实体的批量转换

  3. 具体使用:
    1).注意事项:
    ①lombok版本必须是1.16.16以上,推荐1.18.10
    ②mapstract 版本推荐 1.3.1.Final 及以上
    ③低于以上的版本会出现各种各样的问题,这都是本人踩过的坑
    2)准备工具:
    ①编译器必须安装lombok插件,导入的lombok相关jar包才能正常使用
    在这里插入图片描述
    ②可以安装mapStruct插件来简化代码,本人没有安装,下面代码也是在无mapStruct插件情况下的代码
    在这里插入图片描述

    	3)注解及相关介绍
    

1.默认的映射规则:
1).同类型且同名的属性,会自动映射
2).同名但类型不同,会自动进行类型转换,支持的类型有:
①8种基本类型以及包装类型,String类型之间的转换;
②日期类型和String之间;

2.@Mapper(componentModel = “spring”) 注意这个mapper是mapStruct包下面的,不是mybatis下的
实质就是加了@comment注解
添加componentModel=“spring”就可以是使工具类和Spring结合,利用注入来使用,不添加的话是不会被注入到spring中的
但是@mapper必须要加,这是工具类的入口
添加componentModel=“spring”后,就可以在工具类里使用spring的资源,如controller,server,mapper等资源,其他类也可以像调用controller,server,mapper等那样,利用@Autowired的方式,去定义调用

@Mappings 多个映射规则的集合
@Mapping 映射规则集合中的某一个规则
source 源类中的属性名
target 目标类中的属性名
dateFormat 源→目标日期格式化 使用方式:dateFormat = “yyyy-MM-dd HH:mm:ss”
numberFormat 源→目标数字格式化 使用方式:numberFormat = “#.00”
ignore 忽略掉某属性的复制 使用方式:ignore = true 则该属性被复制忽略

@AfterMapping 表示让mapstruct在调用完自动转换方法之后,会来自动调用本方法
@MappingTarget:表示传来的实体是已经转换后的,是赋值后的,配合AfterMapping 一起使用

@BeanMapping(ignoreByDefault = true) 禁止默认配置,只支持自定义的映射行为 即上述的默认规则失效,配合@Mapping来完成自定义的复制效果

@InheritConfiguration 继承上一转换的配置 如果上一次转换屏蔽了默认的配置规则,那么这次也会屏蔽掉默认的配置规则,如果上一次定义了其他规则,那么这一次也会执行该规则

@InheritInverseConfiguration(name = “XXX”)指定继承那个方法的配置,XXX为某方法名,该注解只会继承指定方法,source和target之间的映射关系,也可以反着,但是不会继承@BeanMapping(ignoreByDefault = true)之类的配置

		4)使用步骤
		概述:其实就是创建一个配置类,用这个配置类去实现相关属性的映射
		①.导入依赖
        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
            <scope>provided</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.mapstruct/mapstruct -->
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>1.3.1.Final</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.mapstruct/mapstruct-processor -->
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>1.3.1.Final</version>
        </dependency>
    </dependencies>
		 		②.2.新建一个抽象类或者接口,作为映射的配置类,并标注@mapper-----mapstruct包下的注解	,来表示这是mapStruct的映射配置
		 		③.写对应的转换方法和映射规则,并创建本类的静态调用实现
		 		④.获取对象并实现。
		5)相关注解的使用例子
					①.首先是例子中要用到的实体
package com.example.demo.beans;
import lombok.Data;
import java.util.Date;
import java.util.List;
@Data
public class CarDTO {

    private Long id;

    private String vin;

    //添加可变类,验证是否是深复制
    private  StringBuilder name;

    private double price;

    private double totalPrice;

    private Date publishDate;

    private String color;

    private String brand;

    private List<PartDTO> partsDTOS;

    private DriverDTO driverDTO;
}

package com.example.demo.beans;
import lombok.Data;

@Data
public class DriverDTO {
    private Long id;
    private String name;
}

package com.example.demo.beans;

import lombok.Data;

@Data
public class PartDTO {
    private Long partId;
    private String partName;
}

package com.example.demo.vo;


import lombok.*;

@Data
public class CarVO {

    private Long id;

    private String vin;

    //添加可变类,验证是否是深复制
    private StringBuilder name;

    private Double price;

    private String totalPrice;

    private String publishDate;

    private String color;

    private String brandName;

    private Boolean hasPart;

    private DriverVO deiverVO;

}

package com.example.demo.vo;

import lombok.Data;

@Data
public class DriverVO {
    private Long deiverId;
    private String fullName;
}

package com.example.demo.vo;

import lombok.Data;
@Data
public class partVO {
    private  Long partId;
    private String partName;
}

package com.example.demo.vo;

import lombok.Data;

@Data
public class VehicleVO {
    private Long id;
    private Double price;
    private String brandName;
}

②.创建配置抽象类

package com.example.demo.convert;
/*
 * 使用mapStruct步骤
 * 1.导入依赖
 * 2.新建一个抽象类或者接口,并标注@mapper-----mapstruct包下的注解
 * 3.写一个转换方法
 * 4.获取对象并使用
 * */

import com.example.demo.beans.CarDTO;
import com.example.demo.beans.DriverDTO;
import com.example.demo.beans.PartDTO;
import com.example.demo.service.UserService;
import com.example.demo.vo.CarVO;
import com.example.demo.vo.DriverVO;
import com.example.demo.vo.VehicleVO;
import org.mapstruct.*;
import org.mapstruct.factory.Mappers;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;
import java.util.Objects;


//添加componentModel=spring就可以是使工具类和Spring结合,就可以利用注入来使用
//不添加的话是不会被注入到spring中的
@Mapper(componentModel = "spring")
public abstract class CarConvert {

    public static CarConvert INSTANCE = Mappers.getMapper(CarConvert.class);//可以静态的调用

/*
待填写内容
*/
    }

③.创建测试类,用到spring注入的在项目的测试文件夹下

@SpringBootTest(classes = {MapStructApplication.class})
public class testSomeThing {

    //注入的方式调用工具类
    @Autowired
    private CarConvert carConvert;
/*
待填写内容
*/

    private CarDTO buildCarDTO() {

        //初始化零件
        //零件一
        PartDTO partDtO1 = new PartDTO();
        partDtO1.setPartId(1L);
        partDtO1.setPartName("多功能方向盘");
        //零件二
        PartDTO partDtO2 = new PartDTO();
        partDtO1.setPartId(2L);
        partDtO1.setPartName("智能车门");
        //填充至list中
        List<PartDTO> partDTOList = new ArrayList<>();
        partDTOList.add(partDtO1);
        partDTOList.add(partDtO2);

        //初始化司机
        DriverDTO driverDTO = new DriverDTO();
        driverDTO.setId(1L);
        driverDTO.setName("王二狗");

        //然后把初始化后的全部填充至实体中
        CarDTO carDTO = new CarDTO();
        carDTO.setId(330L);
        carDTO.setVin("vin123456789");
        carDTO.setName(new StringBuilder("可口可乐"));
        carDTO.setPrice(123789.126d);
        carDTO.setTotalPrice(143789.126d);
        carDTO.setPublishDate(new Date());
        carDTO.setColor("白色");
        carDTO.setBrand("大众");
        carDTO.setPartsDTOS(partDTOList);
        carDTO.setDriverDTO(driverDTO);

        return carDTO;
    }
    }

⑤测试1:@mappings和@afterMapping的使用
配置类里面写

  /*
     *
     *carDTO   ---->  carVO
     * */

    //Mappings   指定映射规则   ,不同属性名之间的映射,属性之间映射格式问题,如时间到字符串格式问题
    @Mappings(
            value = {
                    @Mapping(source = "totalPrice", target = "totalPrice", numberFormat = "#.00"),//保留两位小数
                    @Mapping(source = "publishDate", target = "publishDate", dateFormat = "yyyy-MM-dd HH:mm:ss"),
                    @Mapping(target = "color",ignore = true),//忽略这个属性的映射
                    @Mapping(source = "brand",target = "brandName"),//不同名之间的映射
                    @Mapping(source = "driverDTO",target = "deiverVO")//必须有转换的方法driverDTO2DriverVO
            }
    )
    public abstract CarVO abc(CarDTO carDTO);


    @AfterMapping//表示让mapstruct在调用完自动转换方法之后,会来自动调用本方法
    public void dto2voAfter(CarDTO carDTO, @MappingTarget CarVO carVO){
        //@MappingTarget:表示传来的carVO是已经转换后的,是赋值后的
        System.out.println("========执行AfterMapping==============");
        List<PartDTO> partsDTOS = carDTO.getPartsDTOS();
        carVO.setHasPart(Objects.nonNull(partsDTOS));
    };

然后测试类里面写


    /**
     * 测试@mappings进行单体的转换,同时转换完后CarVO会执行AfterMapping
     */
    @Test
    public void test2() {
        CarDTO carDTO = buildCarDTO();
        CarVO carVO = carConvert.abc(carDTO);
        //改变可变量的值,观察是否是深复制
        StringBuilder name = carVO.getName();
        name.append("公司");
        //很明显是浅复制
        System.out.println(carVO.toString());
        System.out.println(carDTO.toString());
    }

输出为:

========执行AfterMapping==============
CarVO(id=330, vin=vin123456789, name=可口可乐公司, price=123789.126, totalPrice=143789.13, publishDate=2021-02-20 03:21:05, color=null, brandName=大众, hasPart=true, deiverVO=DriverVO(deiverId=1, fullName=王二狗))
CarDTO(id=330, vin=vin123456789, name=可口可乐公司, price=123789.126, totalPrice=143789.126, publishDate=Sat Feb 20 03:21:05 CST 2021, color=白色, brand=大众, partsDTOS=[PartDTO(partId=2, partName=智能车门), PartDTO(partId=null, partName=null)], driverDTO=DriverDTO(id=1, name=王二狗))

⑥测试二:@mapping的单独使用
配置类里面写


    /**
     * driverDTO -> DriverVO
     * @param driverDTO
     * @return
     */
    @Mapping(source = "id",target = "deiverId")
    @Mapping(source = "name",target = "fullName")
    public abstract DriverVO driverDTO2DriverVO(DriverDTO driverDTO);

测试类里面写


    /**
     * 测试@mapping单独使用
     */
    @Test
    public void test2_1(){
        DriverDTO peter = new DriverDTO();
        peter.setId(12L);
        peter.setName("peter");
        DriverVO driverVO = carConvert.driverDTO2DriverVO(peter);
        System.out.println("==driverVO=="+driverVO.toString());

    }

输出为:

==driverVO==DriverVO(deiverId=12, fullName=peter)

⑦测试三:这个批量转换会先在配置类里面寻找单体转换,利用配置类里的单体转换,来完成批量转换
配置类里写

    /**
     * dto2vo这个方法的批量转换
     * 这个批量转换会先在配置类里面寻找单体转换,利用配置类里的单体转换,来完成批量转换
     */
    public abstract  List<CarVO> dtos2vos(List<CarDTO> carDTOs);

测试文件里写


    /**
     * 测试批量转换
     * list<CarDTO>  →   list<CarVO></></>
     */
    @Test
    public void test3() {
        CarDTO carDTO = buildCarDTO();
        List<CarDTO> carDTOLists = new ArrayList<>();
        carDTOLists.add(carDTO);
        carDTOLists.add(carDTO);
        carDTOLists.add(carDTO);
        List<CarVO> carVOList = CarConvert.INSTANCE.dtos2vos(carDTOLists);
        System.out.println(carVOList);
    }

输出为:


========执行AfterMapping==============
========执行AfterMapping==============
========执行AfterMapping==============
[CarVO(id=330, vin=vin123456789, name=可口可乐, price=123789.126, totalPrice=143789.13, publishDate=2021-02-20 03:22:42, color=null, brandName=大众, hasPart=true, deiverVO=DriverVO(deiverId=1, fullName=王二狗)), CarVO(id=330, vin=vin123456789, name=可口可乐, price=123789.126, totalPrice=143789.13, publishDate=2021-02-20 03:22:42, color=null, brandName=大众, hasPart=true, deiverVO=DriverVO(deiverId=1, fullName=王二狗)), CarVO(id=330, vin=vin123456789, name=可口可乐, price=123789.126, totalPrice=143789.13, publishDate=2021-02-20 03:22:42, color=null, brandName=大众, hasPart=true, deiverVO=DriverVO(deiverId=1, fullName=王二狗))]

⑧测试@BeanMapping忽略默认配置规则,顺便测试@InheritConfiguration继承配置注解
配置文件里写


    /**
     * 配置忽略mapstruct的默认映射行为,只映射那些配置了@Mapping的属性
     *@BeanMapping
     */
    @BeanMapping(ignoreByDefault = true)//禁止默认配置,只支持自定义的映射行为
    @Mapping(source = "id",target = "id")//只映射id
    @Mapping(source = "brand",target = "brandName")//只映射brand
    public abstract VehicleVO carDTO2vehicleVO(CarDTO carDTO);




    @InheritConfiguration
    //继承上一转换的配置,例如在测试的例子中,上一次的配置是屏蔽掉默认配置,值转换ID和brandName
    public abstract void updatevehicleVO(CarDTO carDTO,@MappingTarget VehicleVO vehicleVO);

测试文件里写



    /**
     * 测试 @BeanMapping
     */
    @Test
    public void test4() {
        CarDTO carDTO = buildCarDTO();
        VehicleVO vehicleVO = CarConvert.INSTANCE.carDTO2vehicleVO(carDTO);
        System.out.println("========"+vehicleVO.toString());
    }

    /**
     * 测试 @InheritConfiguration  继承配置
     */
    @Test
    public void test5() {
        CarDTO carDTO = buildCarDTO();
        VehicleVO vehicleVO = CarConvert.INSTANCE.carDTO2vehicleVO(carDTO);

        System.out.println("========"+vehicleVO.toString());
        CarDTO carDTO1 = new CarDTO();
        carDTO1.setBrand("迈巴赫");
        //通过carDTO1的属性值来更新已存在的VehicleVO对象
        //继承上一转换的配置,例如在测试的例子中,上一次的配置是屏蔽掉默认配置,值转换ID和brandName
        //所以在输出结果中看不到price的值
        CarConvert.INSTANCE.updatevehicleVO(carDTO1,vehicleVO);
        System.out.println("========"+vehicleVO.toString());
    }

输出为:

========VehicleVO(id=330, price=null, brandName=大众)
========VehicleVO(id=330, price=null, brandName=大众)
========VehicleVO(id=null, price=null, brandName=迈巴赫)

⑨,测试InheritInverseConfiguration继承特定类的配置
配置文件里写


    /**
     * 测试 @InheritInverseConfiguration   反向继承
     *name :指定使用哪一个方法的配置
     */

    @BeanMapping(ignoreByDefault = true)//禁止默认配置,只支持自定义的映射行为
    @InheritInverseConfiguration(name = "carDTO2vehicleVO")
    //指定继承哪一个方法的配置,继承@mapping注解且source和target是相反的
    //不会继承@BeanMapping等其他注解
    public abstract  CarDTO vehicleVO2CarDTO(VehicleVO vehicleVO);

测试文件里写


    /**
     * 测试 @InheritInverseConfiguration  反向继承
     */
    @Test
    public void test6() {
        VehicleVO vehicleVO = new VehicleVO();
        vehicleVO.setId(9999L);
        vehicleVO.setBrandName("别克");
        vehicleVO.setPrice(66554322d);
        CarDTO carDTO = CarConvert.INSTANCE.vehicleVO2CarDTO(vehicleVO);
        System.out.println(carDTO);
    }

输出为:


CarDTO(id=9999, vin=null, name=null, price=0.0, totalPrice=0.0, publishDate=null, color=null, brand=别克, partsDTOS=null, driverDTO=null)

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

PH = 7

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值