代码经验---mapStruct代替BeanUtils进行类之间的属性拷贝

目录

 

前言:

1:如何使用:

1:引入mapStruct和lombok依赖

2:增加转换器(@Mapper)

3:写一个转换方法

4:获取对象INSTANCE并使用

2:几个使用的关键点:

@Mapper

@Mappings和@Mapping(指定属性之间的映射关系)

@AfterMapping 和 @MappingTarget (属性的自定义映射处理)

@BeanMapping(ignoreByDefault 忽略mapstruct默认的映射行为)

@InheritConfiguration

@InheritInverseConfiguration

3:与spring 结合使用

4:举例子:DTO转VO


前言:

在我们进行bean类属性的拷贝的时候,经常使用org.springframework.beans.BeanUtils这个工具类;

当然可能还有apache的BeanUtils;

还有你可以自己写get,set方法来进行属性复制拷贝(代码较多,复杂);

-

BeanUtils缺点:

1:利用反射的机制,性能较差;若是对象的属性超级多的话,这个缺点会被无线的放大;

2:需要【类型】和【名称】都一样才会进行映射,不同名字的还需要自己手动get/set赋值;

如果我们需要实现简单的bean拷贝,选择spring的bean拷贝功能是个不错选择;

当业务量不大时,不管选择哪个框架都没什么问题,只要功能支持就ok了;

但是当数据量大的时候,可能就需要考虑性能问题了;

mapstruct:

1:开发会浪费时间,因为要写转化器类(需要声明bean的转换接口);

2:在添加新的字段的时候也要进行方法的修改,很麻烦;

3:但是优点也是明显的:不需要进行反射,编译后的代码其实就是我们经常写的get/set方法,性能是很高的(性能媲美直接的get/set);

1:如何使用:

1:引入mapStruct和lombok依赖

<org.mapstruct.version>1.3.1.Final</org.mapstruct.version>

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-jdk8</artifactId>
    <version>${org.mapstruct.version}</version>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>${org.mapstruct.version}</version>
    <scope>provided</scope>
</dependency>

2:增加转换器(@Mapper)

新建一个抽象类,或者接口,标注:@Mapper

注意这个Mappeer引入的是:
import org.mapstruct.Mapper;

3:写一个转换方法

方法名字是可以任意的,没有要求

一般常用 dtoToVo  或者其他的;

4:获取对象INSTANCE并使用

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

2:几个使用的关键点:

@Mapper

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

@Mappings和@Mapping(指定属性之间的映射关系)

  • 用于指定属性之间的映射关系
    • 日期格式化:dateFormat="yyyy-MM-dd HH:mm:ss"
    • 数字格式化:numberFormat = "#.00"
    • @Mapping(source = "totalPrice", target = "totalPrice", numberFormat = "#.00")
      @Mapping(source = "createDate", target = "createDate", dateFormat = "yyyy-MM-dd HH:mm:ss")
  • source 或者 target 多余的属性,对方没有,不会报错
  • ignore:用于配置忽略某一个属性,不想转换返回就配置ignore;
    • @Mapping(target = "color", ignore = true)
  • 不同名字的属性,也是可以映射的
    • @Mapping(source = "name", target = "carName")
  • 属性是引用对象的映射
    • 就是说如果一个属性是引用类型的话:默认是没有进行复制的;
      • 对象也可以映射的,需要加下面这个配置
      • @Mapping(source = "carDetailDTO", target = "carDetailVO")
        
      • 这个时候,mapstruct会自动去查找,代码中有没有把CarDetailDTO 转为 CarDetailVO的方法;
      • 所以:我们还需要写这两个对象的转换方法;
  • 批量映射:List<CarDTO>---->List<CarVO>

@AfterMapping 和 @MappingTarget (属性的自定义映射处理)

用于针对mapstruct 处理不了的一些属性的映射;

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

@BeanMapping(ignoreByDefault 忽略mapstruct默认的映射行为)

  • ignoreByDefault:忽略mapstruct的默认映射行为;只映射那些配置了@Mapping的属性
    • 避免不需要的赋值,避免属性覆盖;
    • 就是可能我某一个属性,不需要你mapstruct给我映射过来;

@InheritConfiguration

中文解释:用于继承配置

  • 可用于更新的场景,避免同样的配置写多份

@InheritInverseConfiguration

中文解释:反向继承配置

  • 反向映射,不用反过来再写一遍
  • @InheritConfiguration(name = "") 
  • name 指定使用哪一个方法的配置(方法就是你转换类中写的那些转换方法)写方法的名字
  • 注意:这里只继承@Mapping注解配置,不会继承@BeanMapping

3:与spring 结合使用

@Autowired

private CarConvert carConvert;

这样就可以直接使用这个对象的方法:

carConvert.toCarVo()

这个时候调用上面这个方法是有问题的,

需要在这个类上,跟spring关联起来:

@Mapper(componentModel = "spring")
public interface CarConvert {
}

实质就是给生成的类加了@Component注解;

那么这个时候我们原来写的这句话就不需要了:CarConvert INSTANCE = Mappers.getMapper(CarConvert.class);

直接用【对象.方法】的形式就可以调用方法了;

4:举例子:DTO转VO

先创建一些辅助类:

@Data
public class CarDTO {

    private Long id;

    private Double totalPrice;

    private String createDate;

    private String color;

    private String name;

    private CarDetailDTO carDetailDTO;

    private List<PartDTO> partDTOList;
}

@Data
public class CarVO {

    private Long id;

    private Double totalPrice;

    private Date createDate;

    private String color;

    private String carName;

    private CarDetailVO carDetailVO;

    private List<PartVo> partVoList;

    // 判断partDTOList是否有值
    private Boolean hasPart;
}

@Data
public class CarDetailDTO {

    private Integer id;

    private String code;
}

@Data
public class CarDetailVO {

    private Integer carDetailId;

    private String carDetailCode;
}

@Data
public class PartDTO {

    private String attribute;

    private String attributeName;
}

@Data
public class PartVo {

    private String attribute;

    private String attributeName;
}

/**
 * 宝马车
 */
@Data
public class BaoMaVO {

    private Long id;

    private Double totalPrice;

    private String brandName;
}

创建一个转换器:

@Mapper(componentModel = "spring")
public interface CarConvert {

    // 如果用了上面那个 @Mapper(componentModel = "spring") ,就是跟spring 整合了,其实就不需要用这中方式调用,这行代码就可以注释掉
    CarConvert INSTANCE = Mappers.getMapper(CarConvert.class);

    @Mappings({
            @Mapping(source = "totalPrice", target = "totalPrice", numberFormat = "#.00"),
            @Mapping(source = "createDate", target = "createDate", dateFormat = "yyyy-MM-dd HH:mm:ss"),
            @Mapping(target = "color", ignore = true),
            @Mapping(source = "name", target = "carName"),
            @Mapping(source = "carDetailDTO", target = "carDetailVO") // 引用对象转换
    })
    CarVO toCarVO(CarDTO carDTO);


    /**
     * 这个方法也可以用于上面那个引用对象转换
     */
    @Mapping(source = "id", target = "carDetailId")
    @Mapping(source = "code", target = "carDetailCode")
    CarDetailVO carDetailDTOToCarDetailVo(CarDetailDTO carDetailDTO);

    /**
     * 自定义属性的映射
     *
     * @AfterMapping :表示让mapstruct在调用完成自动转换的方法之后,会来自动调用本方法
     * @MappingTarget :表示传来的carVO对象是已经赋值过的
     */
    @AfterMapping
    public default void carDTOToVOAfter(CarDTO carDTO,@MappingTarget CarVO carVO){
        if (!CollectionUtils.isEmpty(carDTO.getPartDTOList())){
            carVO.setHasPart(true);
        }
    }

    /**
     * 集合批量转换(就是上面这个方法的批量转换-toCarVO)
     */
    List<CarVO> toCarVOList(List<CarDTO> carDTOList);

    /**
     * 忽略mapstruct 的默认的映射行为
     *
     * 下面这个ignore 就是忽略了 totalPrice 属性的映射,不让这个属性值被映射过来;
     * 但是若是一个类中我们需要忽略的属性有几十个,可能要写几十行这个@Mapping(ignore=),非常不方便
     *
     * 所以可以通过@BeanMapping 来进行配置
     */
    @Mapping(source = "totalPrice", target = "totalPrice", ignore = true)
    BaoMaVO toBaoMaVO(CarDTO carDTO);

    /**
     * 配置忽略mapstruct的默认映射行为,只映射那些配置了@Mapping的属性
     */
    @BeanMapping(ignoreByDefault = true)
    @Mapping(source = "id", target = "id") // 这里只映射id属性
    @Mapping(source = "name", target = "brandName") // 这里还映射name属性
    BaoMaVO toBaoMaVO2(CarDTO carDTO);

    /**
     * 更新场景:继承配置,避免同样的配置写多份
     */
    @InheritConfiguration
    void updateBaoMaVo(CarDTO carDTO,@MappingTarget BaoMaVO baoMaVO);

}

测试类:

    public static void main(String[] args) {

        //批量转换(List<CarDTO>---->List<CarVO>)
        List<CarDTO> carDTOList = Lists.newArrayList(); //source 假设这里已经有数据了
        List<CarVO> carVOList = Lists.newArrayList(); // target
        // 以前是用for循环遍历转换
        carDTOList.forEach(carDTO -> {
            CarVO carVO = CarConvert.INSTANCE.toCarVO(carDTO);
            carVOList.add(carVO);
        });
        // mapstruce 专门给我们提供了一个方法,用于转换list的
        List<CarVO> carVOListNew = CarConvert.INSTANCE.toCarVOList(carDTOList);

        // @InheritConfiguration 继承配置
        CarDTO carDTO = new CarDTO();
        //carDTO.setXXX
        //car.set...
        BaoMaVO baoMaVO = CarConvert.INSTANCE.toBaoMaVO2(carDTO);
        System.out.println(baoMaVO); // baoMaVO 没有修改以前的值
        // 希望通过 carDTO2 中的属性值,来更新 baoMaVO 中的属性值
        CarDTO carDTO2 = new CarDTO();
        //carDTO2.setXXX
        CarConvert.INSTANCE.updateBaoMaVo(carDTO2, baoMaVO); // 进行应该
        System.out.println(baoMaVO); // baoMaVO 修改以后的值
    }

其实最常用的就两个:

@Mapping
@Mappings

其他都不太常用,因为我们主要是做bean属性拷贝的,如果需要拷贝后重新复制,也可以用上面的,但是一般都直接在拷贝后自己写set方法了,这样便于阅读;

https://lux-sun.blog.csdn.net/article/details/113946112

https://blog.csdn.net/zhige_me/article/details/80699784

猜你喜欢

转载自blog.csdn.net/u010953880/article/details/117033989