目录
@Mappings和@Mapping(指定属性之间的映射关系)
@AfterMapping 和 @MappingTarget (属性的自定义映射处理)
@BeanMapping(ignoreByDefault 忽略mapstruct默认的映射行为)
前言:
在我们进行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
- 默认映射规则
- 【同类型且同名】的属性,会自动映射;
- mapstruct会自动进行类型转换:
- 8种基本类型 和 对应的包装类型 之间
- 8中基本类型(包括他们的包装类型)和 string 之间
- 日期类型和 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方法了,这样便于阅读;