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
Warning: Undefined variable $return_smiles in /www/wwwroot/wql_luoqin_ltd/wp-content/themes/Sakura/functions.php on line 1109