1. MapStruct的简介
MapStruch官方定义:
- 是什么?:MapStruct是一个代码生成器,它基于约定优于配置的方法,极大地简化了Java bean类型之间映射的实现。生成的映射代码使用简单的方法调用,因此快速、类型安全且易于理解
- 为什么?:多层应用开发通常需要在不同的对象模型(例如实体和dto)之间进行映射。编写这样的映射代码是一项繁琐且容易出错的任务。MapStruct的目标是通过尽可能地自动化来简化这项工作。与其他映射框架相比,MapStruct在编译时生成bean映射,这确保了高性能,允许快速的开发人员反馈和彻底的错误检查
- 怎么做?:MapStruct是一个插入Java编译器的注释处理器,可以在命令行构建(Maven、Gradle等)中使用,也可以在您首选的IDE中使用。MapStruct使用了合理的默认值,但在配置或实现特殊行为时却不太方便
MapStrush的使用场景:VO、BO、DTO、DO等不同数据或业务实体之间转换
不同实体或模型的Convert解决方案:
| 名称 | 描述 |
| MapStruct | 基于jsr269实现在编译期间生成代码、性能高、精细控制、解耦 |
| orika | 能够精细控制、解耦 |
| Spring的BeanUtils体系 | 简单易用,但不能对属性进行定制处理 |
2. 传统方式解决POJO的转换
以一个汽车DTO和VO为例,将DTO转换为VO
① CarDTO
@Data
public class CarDTO {
//编号
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
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DriverDTO { //司机
private Long id;
private String name;
}
③ PartDTO
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PartDTO { //零件
private Long partId;
private String partName;
}
④ CarDriverVO
由于前端显示不需要携带零件对象和司机对象,只需要知道是否有零件和只需要知道司机姓名,所以创建一个VO对象进行前端响应
@Data
public class CarDriverVO {
//编号
private Long id;
//车辆编号
private String vin;
//裸车价格
private double price;
//上路价格
private double totalPrice;
//生成日期
private Date publishDate;
//品牌名称
private String brand;
//是否配置了零件
private boolean partDTOS;
//汽车司机名称
private String driverName;
}
将DTO转化为VO
public class test {
public static void main(String[] args) {
CarDTO carDto = getCarDto();
CarDriverVO carDriverVO = new CarDriverVO();
carDriverVO.setId(carDto.getId());
carDriverVO.setVin(carDto.getVin());
carDriverVO.setPrice(carDto.getPrice());
carDriverVO.setTotalPrice(carDto.getTotalPrice());
carDriverVO.setPublishDate(carDto.getPublishDate());
boolean par = carDto.getPartDTOS()!=null && !carDto.getPartDTOS().isEmpty();
carDriverVO.setPartDTOS(par);
carDriverVO.setDriverName(carDto.getDriverDTO().getName());
}
//实在DTO并返回
public static CarDTO getCarDto(){
CarDTO carDTO = new CarDTO();
carDTO.setId(1L);
carDTO.setVin("湘Ba8921");
carDTO.setPrice(182382.52);
carDTO.setTotalPrice(200000.00);
carDTO.setPublishDate(Date.valueOf(LocalDate.now()));
carDTO.setBrand("日产");
List<PartDTO> array= new ArrayList<>();
PartDTO partDTO = new PartDTO();
partDTO.setPartId(1L);
partDTO.setPartName("方向盘");
array.add(partDTO);
DriverDTO driverDTO = new DriverDTO();
driverDTO.setId(1L);
driverDTO.setName("李某某");
carDTO.setPartDTOS(array);
carDTO.setDriverDTO(driverDTO);
return carDTO;
}
}
缺点:
- 可复用性低,代码不简洁
- 与业务代码耦合性高
3. MapStruct的初体验
步骤:
- 引入依赖
- 新建一个抽象类或者接口并标注@Mapper
- 写一个转换方法
- 获取对象并使用
① 引入依赖(版本可选)
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.0.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.0.Final</version>
</dependency>
② 新建一个抽象类并写一个转换方法
@Mapper
public abstract class CarConvert {
//获取当前对象
public static CarConvert INSTANCE = Mappers.getMapper(CarConvert.class);
//转换方法,参数为原实体对象,返回值为结果对象,方法为转换操作
public abstract CarDriverVO CarDTOToVO(CarDTO carDTO);
}
③ 测试使用
public class Test1 {
public static void main(String[] args) {
CarDriverVO carConvert =CarConvert.INSTANCE.CarDTOToVO(getCarDto());
System.out.println(carConvert);
}
//实在DTO并返回
public static CarDTO getCarDto(){
CarDTO carDTO = new CarDTO();
carDTO.setId(1L);
carDTO.setVin("湘Ba8921");
carDTO.setPrice(182382.52);
carDTO.setTotalPrice(200000.00);
carDTO.setPublishDate(Date.valueOf(LocalDate.now()));
carDTO.setBrand("日产");
List<PartDTO> array= new ArrayList<>();
PartDTO partDTO = new PartDTO();
partDTO.setPartId(1L);
partDTO.setPartName("方向盘");
array.add(partDTO);
DriverDTO driverDTO = new DriverDTO();
driverDTO.setId(1L);
driverDTO.setName("李某某");
carDTO.setPartDTOS(array);
carDTO.setDriverDTO(driverDTO);
return carDTO;
}
}
结果:
CarDriverVO(id=1, vin=湘Ba8921, price=182382.52, totalPrice=200000.0, publishDate=2022-10-06, brand=日产, partexites=false, driverName=null)
4. 自定义指定映射
MapStruct的默认映射规则:
1.同类型且同名的属性,会自动映射
2.mapstruct会自动进行类型转化
- 8种基本类型和它们对应包装类型之间的转换
- 8种基本类型和String类型之间的转换
- 日期类型和String类型之间的转换
@Mappings和@Mapping的功能:自定义指定映射规则
- @Mappings:@Mapping的集合注解
- @Mapping:指定具体的映射规则
@Mappings的属性:
- Mapping[] value():@Mapping集合
@Mapping的属性:
① 基本属性
- source:映射源的属性名(对应方法参数的属性)
- target:最终转换的目标对象的属性名(返回值对象的属性)
② 格式化
- numberFormat():数字格式化
- dateFormat():日期格式化
③ 其他
- boolean ignore():是否忽略该属性
- String defaultValue():添加默认值
- String defaultExpression():添加默认的java表达式,只有在source的值为null时才生效
- String expression():添加java表达式
注:在使用expression表达式时如果引入的方法,需要通过@Mapper中的import进行导入
例:
@Mapper(imports = {LocalDateTime.class})
public abstract class CarMapConvert {
//获取当前对象
public static CarMapConvert INSTANCE = Mappers.getMapper(CarMapConvert.class);
//转换方法,参数为原实体对象,返回值为结果对象,方法为转换操作
@Mappings(value = {
@Mapping(source ="driverDTO.name" ,target = "driverName",expression = "java"),
@Mapping(source = "publishDate",target = "publishDate",defaultExpression = "java(LocalDateTime.now())")
})
public abstract CarDriverVO CarDTOToVO(CarDTO carDTO);
}
5. 映射处理
- @BeforeMapping:在Mapper自动转换之前执行方法
- @AfterMapping:在Mapper自动转换完成之后执行方法
它们类似于String的AOP,在某一个操作完成之前或之后进行相关处理
@MappingTarget:对转换之后的对象进行参数注入赋值,假如需要使用转换后的对象进行操作就使用该注解进行赋值
例:对CarDriverVO的partexites属性是否具体零件进行判断赋值
@Mapper
public abstract class CarConvert {
//获取当前对象
public static CarConvert INSTANCE = Mappers.getMapper(CarConvert.class);
//转换方法,参数为原实体对象,返回值为结果对象,方法为转换操作
public abstract CarDriverVO CarDTOToVO(CarDTO carDTO);
@AfterMapping
public void CarDriverVOHandler(CarDTO carDTO,@MappingTarget CarDriverVO carDriverVO){
System.out.println("执行了后置处理方法");
boolean par = carDTO.getPartDTOS()!=null && !carDTO.getPartDTOS().isEmpty();
carDriverVO.setPartexites(par);
}
}
结果:
执行了后置处理方法 CarDriverVO(id=1, vin=湘Ba8921, price=182382.52, totalPrice=200000.0, publishDate=2022-10-07, brand=日产, partexites=true, driverName=null)
6. 批量映射
6.1 传统方式进行批量转换
① 转换类
@Mapper
public abstract class CarConvert {
//获取当前对象
public static CarConvert INSTANCE = Mappers.getMapper(CarConvert.class);
//转换方法,参数为原实体对象,返回值为结果对象,方法为转换操作
public abstract CarDriverVO CarDTOToVO(CarDTO carDTO);
}
② 批量转换
//DTO集合
List<CarDTO> carDTOList = new ArrayList<>();
carDTOList.add(getCarDto());
//VO集合
List<CarDriverVO> carDriverVOList = new ArrayList<>();
//批量转换成VO集合
for(CarDTO carDTO:carDTOList){
CarDriverVO carConvert = CarConvert.INSTANCE.CarDTOToVO(carDTO);
carDriverVOList.add(carConvert);
}
6.2 MapStruct批量映射
MapStruct会自动读取集合之中的泛型对象,并进行批量转换
例:
① 转换类
@Mapper
public abstract class CarConvertList {
//获取当前对象
public static CarConvertList INSTANCE = Mappers.getMapper(CarConvertList.class);
//转换方法,参数为原实体对象,返回值为结果对象,方法为转换操作
public abstract List<CarDriverVO> CarDTOToVO(List<CarDTO> carDTO);
}
② 测试
//DTO集合 List<CarDTO> carDTOList = new ArrayList<>(); carDTOList.add(getCarDto()); List<CarDriverVO> carConvert = CarConvertList.INSTANCE.CarDTOToVO(carDTOList); System.out.println(carConvert);
7. 映射继承配置
7.1映射继承配置
@InheritConfiguration:映射继承注解,适应于增量更新场景(继承指继承@Mapping注解)
例1:
① 转换类
@Mapper
public abstract class CarInheritConvert {
//获取当前对象
public static CarInheritConvert INSTANCE = Mappers.getMapper(CarInheritConvert.class);
//转换方法,参数为原实体对象,返回值为结果对象,方法为转换操作
public abstract CarDriverVO CarDTOToVO(CarDTO carDTO);
//对属性进行更新方法
abstract public void UpdateCarDriverVO(CarDTO carDTO,@MappingTarget CarDriverVO carDriverVO);
}
② 更新
CarDriverVO carInheritConvert =CarInheritConvert.INSTANCE.CarDTOToVO(getCarDto());
System.out.println("更新前的对象:"+carInheritConvert);
//更新对象
CarDTO carDTO = new CarDTO();
carDTO.setId(1L);
carDTO.setVin("湘A2193");
//通过UpdateCarDriverVO方法进行更新
CarInheritConvert.INSTANCE.UpdateCarDriverVO(carDTO,carInheritConvert);
//更新后的对象
System.out.println("更新后的对象:"+carInheritConvert);
结果:
更新前的对象:CarDriverVO(id=1, vin=湘Ba8921, price=182382.52, totalPrice=200000.0, publishDate=2022-10-07, brand=日产, partexites=false, driverName=null) 更新后的对象:CarDriverVO(id=1, vin=湘A2193, price=0.0, totalPrice=0.0, publishDate=null, brand=null, partexites=false, driverName=null)
更新只是做了重新映射,只有赋值的属性有值,不能继承更新前属性的值,做不到增量更新
@Mapper
public abstract class CarInheritConvert {
//获取当前对象
public static CarInheritConvert INSTANCE = Mappers.getMapper(CarInheritConvert.class);
//转换方法,参数为原实体对象,返回值为结果对象,方法为转换操作
public abstract CarDriverVO CarDTOToVO(CarDTO carDTO);
//对属性进行更新方法
@InheritConfiguration
@Mappings(value = {
@Mapping(source = "price",target = "price",ignore = true),
@Mapping(source = "totalPrice",target = "totalPrice",ignore = true),
})
abstract public void UpdateCarDriverVO(CarDTO carDTO,@MappingTarget CarDriverVO carDriverVO);
}
② 更新
CarDriverVO carInheritConvert =CarInheritConvert.INSTANCE.CarDTOToVO(getCarDto());
System.out.println("更新前的对象:"+carInheritConvert);
//更新对象
CarDTO carDTO = new CarDTO();
carDTO.setId(1L);
carDTO.setVin("湘A2193");
//通过UpdateCarDriverVO方法进行更新
CarInheritConvert.INSTANCE.UpdateCarDriverVO(carDTO,carInheritConvert);
//更新后的对象
System.out.println("更新后的对象:"+carInheritConvert);
结果:
更新前的对象:CarDriverVO(id=1, vin=湘Ba8921, price=182382.52, totalPrice=200000.0, publishDate=2022-10-07, brand=日产, partexites=false, driverName=null) 更新后的对象:CarDriverVO(id=1, vin=湘A2193, price=182382.52, totalPrice=200000.0, publishDate=null, brand=null, partexites=false, driverName=null)
7.2 反向映射继承配置
还有另外一个类似的场景,就是编写映射函数将DTO转为VO,以及将VO转为DTO
@Mapper(componentModel = "spring")
public interface PatientMapper {
@Mapping(source = "dateOfBirth", target = "dateOfBirth", dateFormat = "dd/MMM/yyyy")
Patient toModel(PatientDto patientDto);
@Mapping(source = "dateOfBirth", target = "dateOfBirth", dateFormat = "dd/MMM/yyyy")
PatientDto toDto(Patient patient);
}
两个方法的配置不会是完全相同的,实际上,它们应该是相反的。将DTO转为VO,以及将 VO转为DTO——映射前后的字段相同,但是源属性字段与目标属性字段是相反的
我们可以在第二个方法上使用@InheritInverseConfiguration注解,避免写两遍映射配置:
@Mapper(componentModel = "spring")
public interface PatientMapper {
@Mapping(source = "dateOfBirth", target = "dateOfBirth", dateFormat = "dd/MMM/yyyy")
Patient toModel(PatientDto patientDto);
@InheritInverseConfiguration
PatientDto toDto(Patient patient);
}
8. 忽略默认映射行为
@BeanMapping会忽略默认的MapStruct产生的映射,只有通过@Mapping指定的映射才会生效
例1:
① 转换类
@Mapper
public abstract class CarConvert {
//获取当前对象
public static CarConvert INSTANCE = Mappers.getMapper(CarConvert.class);
//转换方法,参数为原实体对象,返回值为结果对象,方法为转换操作
@BeanMapping
public abstract CarDriverVO CarDTOToVO(CarDTO carDTO);
}
② 测试
public static void main(String[] args) {
CarDriverVO carConvert = CarConvert.INSTANCE.CarDTOToVO(getCarDto());
System.out.println(carConvert);
}
结果为空,没有映射
9. MapStruct和Spring整合使用
之前我们一直在通过getMapper()方法访问生成的映射器:
DoctorMapper INSTANCE = Mappers.getMapper(DoctorMapper.class);
但是,如果你使用的是Spring,只需要简单修改映射器配置,就可以像常规依赖项一样注入映射器。
修改 DoctorMapper 以支持Spring框架:
@Mapper(componentModel = "spring")
public interface DoctorMapper {
}
在@Mapper注解中添加(componentModel = "spring"),是为了告诉MapStruct,在生成映射器实现类时,我们希望它能支持通过Spring的依赖注入来创建。现在,就不需要在接口中添加 INSTANCE 字段了。
这次生成的 DoctorMapperImpl 会带有 @Component 注解:
@Component
public class DoctorMapperImpl implements DoctorMapper {
}
只要被标记为@Component,Spring就可以把它作为一个bean来处理,你就可以在其它类(如控制器)中通过@Autowire注解来使用它:
@Controllerpublic class DoctorController() {
@Autowired
private DoctorMapper doctorMapper;
}
如果你不使用Spring, MapStruct也支持Java CDI:
@Mapper(componentModel = "cdi")
public interface DoctorMapper {
}






Comments | NOTHING