本文为 【Mapstruct】 工具的相关知识,Mapstruct是一个生成类型安全,高性能且无依赖的 JavaBean 映射代码的注解处理器,可以生成 JavaBean 之间那的映射代码,类型安全,高性能,无依赖性。
📌博主主页:一个肥鲇鱼
👉开发中的坏习惯:程序员的坏习惯,你占了几个!
👉感受 Lambda 之美:体验一下Lambda之美吧,优雅编程
👉统一返回格式:SpringBoot统一返回格式(优雅实现)
前言
先来说一下为什么使用Mapstruct?
对于代码中 JavaBean之间的转换, 在编程中是一件很费时费力的。在开发的时候业务代码之间有很多的 JavaBean 之间的相互转化, 非常的影响观感,却又不得不存在,而手动的去(settter/getter)繁琐且易于出错。
优点分析
性能高
这是相对反射来说的,反射需要去读取字节码的内容,花销会比较大。而通过 MapStruct 来生成的代码,其类似于人手写(settter/getter)。速度上可以得到保证。
代码独立
生成的代码是对立的,没有运行时的依赖
当出现以下情况,程序在编译期间就会给出提示。
映射不完整(并非所有目标属性都被映射)
映射不正确(找不到合适的映射方法或类型转换)
易于Debug
ps:如果是配合Lombok使用的话,版本最好在1.16.16以上,否则Mapstruct编译会出现问题
依赖与插件
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.2.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.2.Final</version>
</dependency>
( mapstruct support )插件不是必须,但挺好用
注解 | 参数 | 解释 |
@Mapping | 当属性在目标实体种具有不同的名称时,可以通过@Mapping注释指定其名称 | |
@Mapping | source() | 源目标实体属性 |
@Mapping | target() | 目标实体属性 |
@Mapping | defaultValue() | 默认值:源目标属性为null时,添加默认值 |
@Mapping | dateFormat() | 如果属性从字符串映射到日期,则该格式字符串可由SimpleDateFormat处理,反之亦然 |
注入方式
import org.mapstruct.Mapper;
/**
* @Author: 一个肥鲇鱼
*/
@Mapper
public interface BeanConvert {
BeanConvert COPY_PROPERTIES = Mappers.getMapper(BeanConvert.class);
}
案例
实体类/VO类
可以发现UserVo的时间属性与User的时间属性名称不一致且基本类型也不一致,这时候就用到了dateFormat()
name:如果源属性为null,会赋上defaultValue中的默认值
/**
* @Author: 一个肥鲇鱼
*/
@Mapper
public interface BeanConvert {
BeanConvert COPY_PROPERTIES = Mappers.getMapper(BeanConvert.class);
/**
* 将UserVo属性赋值到User
*
* 原属性name为null时,赋上默认值(一个肥鲇鱼)
* 将createTime映射到time,并格式化为yyyy-MM-dd
*
* @param vo
* @return
*/
@Mappings({
@Mapping(source = "name", target = "name", defaultValue = "一个肥鲇鱼"),
@Mapping(source = "createTime", target = "time", dateFormat = "yyyy-MM-dd"),
})
User convertUser(UserVo vo);
}
测试
@Test
void test() {
UserVo vo = new UserVo();
vo.setId(1);
vo.setName(null);
vo.setCreateTime(new Date());
// 调用转换方法
User user = BeanConvert.COPY_PROPERTIES.convertUser(vo);
System.out.println(user);// User{id=1, name='一个肥鲇鱼', time='2023-02-09'}
}
解释
Mapstruct在编译中执行,会生成一个类似于手写的(settter/getter)类。如下图:
List互转
/**
* @Author: 一个肥鲇鱼
*/
@Mapper
public interface BeanConvert {
BeanConvert COPY_PROPERTIES = Mappers.getMapper(BeanConvert.class);
/**
* list互转
*
* @param voList
* @return
*/
@Mappings({
@Mapping(source = "name", target = "name", defaultValue = "一个肥鲇鱼"),
@Mapping(source = "createTime", target = "time", dateFormat = "yyyy-MM-dd"),
})
List<User> convertUserList(List<UserVo> voList);
}
测试
@Test
void test() {
List<UserVo> voList = new ArrayList<>();
for (int i = 0; i <= 2; i++) {
UserVo vo = new UserVo();
vo.setId(i + 1);
vo.setName("一个肥鲇鱼");
vo.setCreateTime(new Date());
voList.add(vo);
}
// 调用转换方法
List<User> userList = BeanConvert.COPY_PROPERTIES.convertUserList(voList);
System.out.println(userList);
// [User{id=1, name='一个肥鲇鱼', time='2023-02-09'},User{id=2, name='一个肥鲇鱼', time='2023-02-09'}, User{id=3,
// name='一个肥鲇鱼', time='2023-02-09'}]
}
编译后的文件
多个源对象映射
再新增一个实体类(UserTo)
/**
* @Author: 一个肥鲇鱼
*/
@Mapper
public interface BeanConvert {
BeanConvert COPY_PROPERTIES = Mappers.getMapper(BeanConvert.class);
/**
* 将to的id赋给User,将vo的name赋给User
*
* @param vo
* @param to
* @return
*/
@Mappings({
@Mapping(source = "to.id",target = "id"),
@Mapping(source = "vo.name",target = "name"),
})
User convertUserTwo(UserVo vo, UserTo to);
}
@Test
void test() {
UserVo vo = new UserVo();
vo.setId(1);
vo.setName("一个肥鲇鱼");
UserTo to = new UserTo();
to.setId(111);
to.setName("张三");
// 调用转换方法
User user = BeanConvert.COPY_PROPERTIES.convertUserTwo(vo, to);
System.out.println(user); // User{id=111, name='一个肥鲇鱼', time='null'}
}
使用其他值
虽然UserVo有id属性,但映射器会优先使用convertUser方法里面的id参数去映射
/**
* @Author: 一个肥鲇鱼
*/
@Mapper
public interface BeanConvert {
BeanConvert COPY_PROPERTIES = Mappers.getMapper(BeanConvert.class);
@Mapping(source = "id", target = "id")
User convertUser(UserVo vo, Integer id);
}
自定义方法
工具类
import java.text.SimpleDateFormat;
import java.util.Date;
import org.mapstruct.Named;
/**
* @Author: 一个肥鲇鱼
*/
public class DateUtils {
@Named("dateToString")
public static String dateToString(Date date){
return date == null ? "": new SimpleDateFormat("yyyy-MM-dd").format(date);
}
}
/**
* @Author: 一个肥鲇鱼
*/
@Mapper(uses = {DateUtils.class})
public interface BeanConvert {
BeanConvert COPY_PROPERTIES = Mappers.getMapper(BeanConvert.class);
/**
* 使用自定义方法,处理时间
* @param vo
* @return
*/
@Mapping(source = "createTime",target = "time",qualifiedByName = "dateToString")
User convertUserTwo(UserVo vo);
}
常见问题
问题一:
No property named “XXX“ exists in source parameter(s). Did you mean “null“?
根本原因:看一下项目的/classes里面,看对应的类是不是编译生成了,或者说类是有的,但是里面没有geter/setter等方法(如果使用Lombok那么主要是Lombok的版本原因)未使用Lombok的,注意实体类中的geter/setter有没有遗漏。
问题二
Couldn’t retrieve @Mapper annotation
两种情况
- 包冲突:项目中使用了swagger,swagger里面也包含mapstruct,排除掉就好
- 导入mapstruct项目时,同时使用了
mapstruct
和mapstruct-processor
但是版本不一致,将版本号改为一样即可。