本文已参与「新人创作礼」活动,一起开启掘金创作之路
本文已参与「新人创作礼」活动,一起开启掘金创作之路
本文已参与「新人创作礼」活动,一起开启掘金创作之路
本文已参与「新人创作礼」活动,一起开启掘金创作之路
本文已参与「新人创作礼」活动,一起开启掘金创作之路
MapStruct的官网网址:
\
研究目的
-
看这个是不是bean 拷备属性的类库, 研究下怎么用,写点代码样例,文档出来.
-
对比下 其与其它bean拷备方式 有什么优缺点. 如apache的 BeanUtil , hutool的, spring的.
-
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的属性名字
这里注意点:
- 如果DTO和VO的属性名字相同,可以不写,Mapping直接可以映射
- 如果DTO和VO的属性名字相同,但是你还想要按照你的规则来映射,只写target="属性名"即可
关键字说明:
- numberFormat:转换后小数点保留后两位
- dateFormat:转换后的日期规定格式
- ignore:如果你不想映射该属性,设置ignore=true,转换后的结果为null
**
**
2.注解说明
- @AfterMapping:表示让mapstruct在调用完自动转换的方法之后,会来自动调用本方法
- @MappingTarget:表示传来的carVO对象已经赋值的
- @BeanMapping:配置忽略mapstruct的默认映射行为,只映射带有Mapping的属性,防止不同类中的相同属性
- @InheritInverseConfiguration:反向继承
-
- name:指定使用哪一个方法的配置,然后写方法的名字就可以了