🎄概述
随着业务开发变得复杂 Pojo类之间转化的繁琐操作占用了程序员大量的时间 而这些代码没有直接的业务价值 因此人们开发出很多
用于Pojo类之间转换的框架
用于简化开发 从而让程序员能够把更多的时间投入到实际业务的开发上面MapStruct出生之前,人们一直用BeanUtils来简化类之间的转换
MapStruct的性能远远高于BeanUtils,下面是 https://juejin.cn/post/7140149801991012365 文章作者的测试结果 根据测试结果,可以看出随着属性个数的增加,BeanUtils的耗时也在增加,并且BeanUtils的耗时跟属性个数成正比,而MapStruct的耗时却一直是1秒,所以从对比数据可以看出MapStruct是非常优秀的,其性能远远超过BeanUtils 现在 程序员们
首选MapStruct来简化类之间的转换操作
🎄 MapStruct 入门实操 + SpringBoot
🍭创建项目搭建环境
(1)创建项目
(2)引入需要的依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.bigbigmeng</groupId>
<artifactId>mapstrcut-start-20231206</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<mapstruct.version>1.3.1.Final</mapstruct.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--springboot工程-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
<!--mapstruct依赖-->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</dependency>
</dependencies>
</project>
🍭创建测试用的Pojo类
这里将使用Car小轿车、Driver司机、Part车上的零件 等类来演示MapStruct的强大功能
📑 PartDto类
/**
@author Liu Xianmeng
@createTime 2023/12/6 17:34
@instruction 汽车零件DTO类
*/
@SuppressWarnings({"all"})
@Data
public class PartDTO {
/**
* 汽车零件的id
*/
private Long partDtoId;
/**
* 零件的名字 比如:多功能方向盘
*/
private String partDtoName;
/**
* 零件的生产日期
*/
private Date partDtoProductionDate;
}
📑 PartVo类
package com.bigbigmeng.dto;
/**
@author Liu Xianmeng
@createTime 2023/12/6 17:36
@instruction 汽车零件VO类
*/
@SuppressWarnings({"all"})
@Data
public class PartVo {
/**
* 汽车零件的id
*/
private Long partVoId;
/**
* 零件的名字 比如:多功能方向盘
*/
private String partVoName;
/**
* 零件的生产日期
*/
private Date partVoProductionDate;
}
创建完两个类之后 项目文件的结构如下
🍭使用MapStruct创建转换工具类
接下来创建一个PartConvert类 用来转化不同的Part对象 比如上面的PartDTO和PartVO对象的互相转化
📑 PartConverter类
PartConverter类可以实现PartDTO和PartVO对象的互相转化 这里就是🎯实际运用
MapStruct
的地方了【使用MapStruct的步骤如下】
(1) 引入依赖(之前的操作已经引入) (2) 新建一个抽象类或者接口并标注 @Mapper (3) 写一个转换方法 方法名规范命名以方便自己使用 (4) 创建PartConverter对象命名为PART_CONVERTER(也可以使用其他命名)供其他(测试)类使用
package com.bigbigmeng.converter;
import com.bigbigmeng.dto.PartDTO;
import com.bigbigmeng.vo.PartVo;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
/**
@author Liu Xianmeng
@createTime 2023/12/6 17:43
@instruction 汽车零件转化器类 -> 作为java对象转换的工具类进行使用
【使用MapStruct的步骤如下】
(1) 引入依赖(之前的操作已经引入)
(2) 新建一个抽象类或者接口并标注 @Mapper
(3) 写一个转换方法 方法名规范命名以方便自己使用
(4) 创建PartConverter对象命名为PART_CONVERTER(也可以使用其他命名)供其他(测试)类使用
*/
@Mapper(componentModel = "spring")
@SuppressWarnings({"all"})
public abstract class PartConverter {
// 创建一个PartConverter转换器对象 供其他类使用
public static PartConverter PART_CONVERTER = Mappers.getMapper(PartConverter.class);
/**
* 用于将PartVO对象转换为PartDTO对象
*
* @param partVo
* @return
*/
@Mapping(source = "partVoId", target = "partDtoId")
@Mapping(source = "partVoName", target = "partDtoName")
public abstract PartDTO partVo2PartDto(PartVo partVo);
}
到这里 一个使用MapStruct相关注解写的java对象转换器类就写好了 后面可以直接使用 使用之前还需要创建一个主启动类 MapStructApplication
📑 MapStructApplication类
/**
@author Liu Xianmeng
@createTime 2023/12/6 18:05
@instruction
*/
@SuppressWarnings({"all"})
@SpringBootApplication
public class MapStructApplication {
public static void main(String[] args) {
SpringApplication.run(MapStructApplication.class);
}
}
目前的项目结构如下
⚡测试使用PartConverter
类
接下来就来实际使用刚才所创建的
PartConverter
类
生成测试类
PartConverterTest
package com.bigbigmeng.converter;
@SpringBootTest(classes = MapStructApplication.class)
class PartConverterTest {
@Resource
PartConverter partConverter;
@Test
void partVo2PartDto() {
// 创建一个PartVo对象
PartVo partVo = new PartVo();
partVo.setPartVoId(100L);
partVo.setPartVoName("小黄鸭");
partVo.setPartVoProductionDate(new Date());
// 获取转换结果
PartDTO partDto = partConverter.partVo2PartDto(partVo);
// 断言转换后的partDto对象的属性值是否符合预期
Assertions.assertEquals(100L, partDto.getPartDtoId());
Assertions.assertEquals("小黄鸭", partDto.getPartDtoName());
// 打印转换后的结果
System.out.println("partVo = " + partVo);
System.out.println("partDto = " + partDto);
}
}
对partVo2PartDto方法进行测试 ->
🌱入门实操总结
再来回顾PartConverter类 -> 简单的配置就能实现java类之间的互相转换 ✅一次配置 永久使用 省去1了业务开发中大量无意义的java类的转换工作 关于MapStruct的其他用法和嵌套对象转换 后面紧接着进行实操
@Mapper(componentModel = "spring")
public abstract class PartConverter {
// 创建一个PartConverter转换器对象 供其他类使用
public static PartConverter PART_CONVERTER = Mappers.getMapper(PartConverter.class);
/**
* 用于将PartVO对象转换为PartDTO对象
*/
@Mapping(source = "partVoId", target = "partDtoId")
@Mapping(source = "partVoName", target = "partDtoName")
public abstract PartDTO partVo2PartDto(PartVo partVo);
}
🎄 MapStruct映射规则
🍭同类型且同名的属性自动映射
如果PartDTO和PartVO都有一个类型为long的testField属性,即使不使用@Mapping指定PartDTO的testField属性也能映射到PartVO的testField属性 接下来进行验证
首先 给两个类🎯添加testField属性
public class PartDTO {
/**
* 添加测试属性 testField
*/
private Long 🎯testField;
...
}
public class PartVO {
/**
* 添加测试属性 testField
*/
private Long 🎯testField;
...
}
然后在测试方法中给partVo对象的testField属性赋值
@Test
void partVo2PartDto() {
// 创建一个PartVo对象
PartVo partVo = new PartVo();
...
partVo.setTestField(123L);
// 获取转换结果
PartDTO partDto = partConverter.partVo2PartDto(partVo);
// 断言转换后的partDto对象的属性值是否符合预期
...
}
测试执行
🍭MapStruct会自动进行类型转换
(1)MapStruct支持8种基本类型和他们对应的包装类型之间的自动转换
(2)MapStruct支持8种基本类型(包括他们的包装类型)和String类型之间的自动转换
(3)MapStruct支持日期类型和String之间的自动转换
🍭@Mappings和@Mapping
@Mappings
@Mappings相当于可以把多个@Mapping进行包含 效果是一样的
@Mappings(
value = {
@Mapping(source = "partVoId", target = "partDtoId"),
@Mapping(source = "partVoName", target = "partDtoName"),
@Mapping(source = "partVoProductionDate", target = "partDtoProductionDate"),
@Mapping(source = "length", target = "length", numberFormat = "#.00")
}
)
🍭数字格式化:numberFormat = “#.00”
给pojo类添加length属性
public class PartDTO {
/**
* 零件的长度 Double类型属性 验证数字格式化
*/
private double length;
...
}
public class PartVO {
/**
* 零件的长度 Double类型属性 验证数字格式化
*/
private String length;
...
}
给映射工具类的添加映射方法🎯partDto2PartVo
@Mapper(componentModel = "spring")
@SuppressWarnings({"all"})
public abstract class PartConverter {
@Mapping(source = "length",target = "length",numberFormat = "#.00")
public abstract PartVo 🎯partDto2PartVo(PartDTO partDTO);
}
添加测试方法
@Test
void partDto2PartVo() {
PartVo rst = PartConverter.PART_CONVERTER.partDto2PartVo(getAPartDto());
System.out.println(rst);
}
PartDTO getAPartDto() {
PartDTO partDTO = new PartDTO();
partDTO.setLength(8888.88888);
return partDTO;
}
执行测试
查看生成的源码的处理
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2023-12-08T10:31:48+0800",
comments = "version: 1.3.1.Final, compiler: javac, environment: Java 1.8.0_341 (Oracle Corporation)"
)
@Component
public class PartConverterImpl extends PartConverter {
...
@Override
public PartVo partDto2PartVo(PartDTO partDTO) {
if ( partDTO == null ) {
return null;
}
PartVo partVo = new PartVo();
/**
* 🎯对dto传入的double进行格式化处理 处理后赋值给vo对象
*
* 🎯🎯🎯这里一定要❗❗❗注意 dto的length属性是double类型 而vo的length属性是String类型
*/
partVo.setLength( new DecimalFormat( "#.00" ).format( partDTO.getLength() ) );
partVo.setTestField( partDTO.getTestField() );
return partVo;
}
}
🍭source和target多余的属性对方没有是不会报错的
🎄 MapStruct嵌套转换
🍭概述和准备
当转换的对象为一个复合对象的时候(一个java对象包含另一个java对象作为属性) 只要做了响应的配置 MapStruct就会自动进行嵌套转换 例如下面的例子:一个汽车对象包含1个车座对象 创建
CarDTO
类和CarVO
类
/**
@author Liu Xianmeng
@createTime 2023/12/8 13:33
@instruction 小轿车DTO
*/
@SuppressWarnings({"all"})
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class CarDTO {
// 车座
private PartDTO carPartDtoSeat;
// 车的品牌
private String carDtoBrand;
}
/**
@author Liu Xianmeng
@createTime 2023/12/8 13:37
@instruction 汽车VO
*/
@SuppressWarnings({"all"})
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class CarVO {
// 车座
private PartDTO carPartVoSeat;
// 车的品牌
private String carVoBrand;
}
然后创建新的转换类
CarConverter
并写一个新的转换方法carDto2carVo、做mapping注解配置
/**
@author Liu Xianmeng
@createTime 2023/12/8 13:48
@instruction
*/
@SuppressWarnings({"all"})
@Mapper(componentModel = "spring")
public abstract class CarConverter {
// 创建一个CarConverter转换器对象 供其他类使用
public static CarConverter CAR_CONVERTER = Mappers.getMapper(CarConverter.class);
/**
* carDto -> carVo
*/
@Mapping(source = "carDtoBrand", target = "carVoBrand")
@Mapping(source = "carPartDtoSeat", target = "carPartVoSeat")
public abstract CarVO carDto2carVo(CarDTO carDto);
}
在测试之前 还需要对🎯
partDto2PartVo
方法做完整的配置
/**
@author Liu Xianmeng
@createTime 2023/12/6 17:43
@instruction 汽车零件转化器类 -> 作为java对象转换的工具类进行使用
*/
@Mapper(componentModel = "spring")
@SuppressWarnings({"all"})
public abstract class PartConverter {
...
/**
* PartDTO -> PartVO
*/
@Mapping(source = "partDtoId", target = "partVoId")
@Mapping(source = "partDtoName", target = "partVoName")
@Mapping(source = "length",target = "length",numberFormat = "#.00")
🎯public abstract PartVO partDto2PartVo(PartDTO partDTO);
}
⚡执行测试
编写测试类
package com.bigbigmeng.converter;
...
@SpringBootTest(classes = MapStructApplication.class)
class CarConverterTest {
@Resource
CarConverter carConverter;
@Test
void carDto2carVo() {
CarDTO carDto = new CarDTO();
carDto.setCarDtoBrand("兰博基尼");
carDto.setCarPartDtoSeat(getACarSeatPartDto());
CarVO carVO = carConverter.carDto2carVo(carDto);
System.out.println(carVO);
}
/**
* 获取一个车座对象
* @return
*/
PartDTO getACarSeatPartDto() {
PartDTO partDTO = new PartDTO();
partDTO.setLength(8888.88888);
partDTO.setPartDtoId(123L);
partDTO.setPartDtoName("车座");
return partDTO;
}
}
执行测试方法
🎄 @AfterMapping && @MappingTarget
🍭概述和准备
有时候DTO和VO之间的的属性没有直接的映射关系 例如下面 给CarVO添加一个hasSeatPart属性 为布尔类型 表示这辆车有没有车座
/**
@author Liu Xianmeng
@createTime 2023/12/8 13:37
@instruction 汽车VO
*/
public class CarVO {
// 车座
private PartDTO carPartVoSeat;
// 车的品牌
private String carVoBrand;
// 车是否有车座
private boolean hasSeatPart;
}
这个时候 如果我们想要根据DTO中是否存在车座组件来映射CarVO中的hasSeatPart属性 就需要完成一些自定义属性的转换 @AfterMapping && @MappingTarget 可以帮我们做到
/**
@author Liu Xianmeng
@createTime 2023/12/8 13:48
@instruction
*/
@SuppressWarnings({"all"})
@Mapper(componentModel = "spring")
public abstract class CarConverter {
...
/**
* 表示让mapstruct在调用完自动转换方法之后 再紧接着调用本方法 完成一些自定义属性的转换
* 例如: 根据DTO中是否存在车座组件来映射CarVO中的hasSeatPart属性
*
* @MappingTarget -> 表示传来的catVo对象是已经赋过值的
* @param carDto
* @param carVo
*/
@AfterMapping
public void carDto2carVoAfter(CarDTO carDto, @MappingTarget CarVO carVo) {
// 获取dto中的车座组件属性
PartDTO carPartDtoSeat = carDto.getCarPartDtoSeat();
// 如果车座组件不为空 则将carVo的hasSeatPart属性赋值为true
if(carPartDtoSeat != null && !carPartDtoSeat.getPartDtoName().equals("")) {
carVo.setHasSeatPart(true);
} else {
carVo.setHasSeatPart(false);
}
}
}
⚡执行测试
🎄 MapStruct批量装换
🍭概述和准备
对🎯批量pojo对象进行转换
/**
@author Liu Xianmeng
@createTime 2023/12/6 17:43
@instruction 汽车零件转化器类 -> 作为java对象转换的工具类进行使用
*/
@Mapper(componentModel = "spring")
@SuppressWarnings({"all"})
public abstract class PartConverter {
...
/**
* 批量转换
*/
public abstract List<PartVO> 🎯partDtos2partVos(List<PartDTO> partDTOS);
}
⚡执行测试
编写测试方法
@SpringBootTest(classes = MapStructApplication.class)
class PartConverterTest {
@Resource
PartConverter partConverter;
@Test
void partDtos2partVos() {
// 获取1个车座对象 和1个车门对象
List<PartDTO> partDtosList = new ArrayList<>();
PartDTO partDTO_001 = getACarSeatPartDto();
partDTO_001.setPartDtoName("车座001");
partDtosList.add(partDTO_001);
PartDTO partDTO_002 = getACarDoorPartDto();
partDTO_002.setPartDtoName("车门002");
partDtosList.add(partDTO_002);
// 将两个对象转换为PartVO对象
List<PartVO> partVOS = partConverter.partDtos2partVos(partDtosList);
System.out.println(partVOS);
}
}
执行测试
🎄 @BeanMapping注解
🍭概述和准备
@BeanMapping注解可以指定不执行默认的映射规则 如:
@BeanMapping(ignoreByDefault = true)
指定之后 只有被@Mapping注解配置的属性会被映射 其他属性均为空 下面举例 :修改partDto2PartVo
方法,加上@BeanMapping(ignoreByDefault = true)修饰 并且只指定车组件的名字属性的映射
/**
* PartDTO -> PartVO
*/
@BeanMapping(ignoreByDefault = true) // 取消默认的映射
@Mapping(source = "partDtoName", target = "partVoName") // 只指定车组件的名字属性的映射
public abstract PartVO partDto2PartVo(PartDTO partDTO);
运行刚才的批量转换
partDtos2partVos
方法 查看运行结果