【代码优化】Bean映射之MapStruct

一、背景

领域模型相互转换就只能靠手工的 get()/set()

普遍的做法有以下几种:

  1. 手工 get()/set()
  2. 构造器;
  3. BeanUtils 工具类(ApacheSpring 都包含该工具类,使用方式稍稍不同);
  4. Builder 模式。

这些方式都存在一些缺点:耦合性强,手工 get()/set() 经常丢参数,或者搞错参数值....

本文推荐一种效率较高的方式:MapStruct

二、理论基础

MapStruct 是一个自动生成 Bean 映射类的代码生成器MapStruct 还能够在不同的数据类型之间进行转换。

2.1 pom.xml

<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.4.2.Final</version>
</dependency>

2.2 注解关键词

  • @Mapper:只有在接口加上这个注解, MapStruct 才会去实现该接口;
  • @Mapping:属性映射,若源对象属性与目标对象名字一致,会自动映射对应属性:
    1. source:源属性;
    2. target:目标属性;
    3. dateFormat:字符串与日期之间相互转换;
    4. ignore: 某个属性不想映射,可以加上 ignore=true
    5. expression:自定义指定的映射方法;
  • @Mappings:配置多个@Mapping
  • @MappingTarget:映射到现有示例。

2.3 工作原理

我们要做的就是定义一个 mapper 接口,该接口声明任何所需的映射方法。在编译期间,MapStruct 将生成此接口的实现。此实现使用普通的Java方法调用来在源对象和目标对象之间进行映射。

三、MapStruct 实践

3.1 基本准备

  • 新增三个数据库 DO 类:

用户信息:

@Data
public class UserInfoDO { private Long id; private String userName; private String password; private String phoneNum; private Date gmtBroth; private RoleDO role; public UserInfoDO() { } public UserInfoDO(RoleDO role,Long id,String userName,String password,String phoneNum,Date gmtBroth) {
this.role = role;
this.id = id;
this.userName = userName;
this.password = password;
this.phoneNum = phoneNum;
this.gmtBroth = gmtBroth;
}
}

用户补充信息:

@Data
public class UserExtInfoDO { private String favorite; public UserExtInfoDO() { } public UserExtInfoDO(String favorite) {
this.favorite = favorite;
}
}

角色信息:

@Data
public class RoleDO { private Long id;
private String roleName;
private String description; public RoleDO() { } public RoleDO(Long id, String roleName, String description) {
this.id = id;
this.roleName = roleName;
this.description = description;
}
}
  • 新增一个数据传输 DTO 类:
@Data
public class UserInfoDTO {
/**
* 用户id
*/
private Long userId;
/**
* 用户名
*/
private String userName;
/**
* 用户名
*/
private String password; /**
* 生日
*/
private String brothStr; /**
* 手机号
*/
private String phoneNum; /**
* 角色名
*/
private String roleName; /**
* 喜好
*/
private String favorite; }
  • 新增一个加密工具类
public class Base64Util {

    public static String encode(String str) {
BASE64Encoder encoder = new BASE64Encoder();
String encode = encoder.encode(str.getBytes());
return encode;
}
}
  • 添加映射接口
@Mapper
public interface MapstructConvert { /**
* 获取该类自动生成的实现类的实例
*/
MapstructConvert INSTANCE = Mappers.getMapper(MapstructConvert.class);
}
  1. 添加一个 interface 接口,使用 MapStruct@Mapper 注解修饰;
  2. 使用 Mappers 添加一个 INSTANCE 实例(也可以使用 Spring 注入,后面会扩展)。

3.2 一对一映射

  • 自定义转换时间格式

通过 dateFormat = "xx" 指定映射的日期格式。

  • 指定默认值

如果该值为空,则使用指定的默认值,如:defaultValue = "-"

  • 忽略不映射的字段

可以通过 ignore = true 指定不需要映射的属性,如: @Mapping(target = "password", ignore = true)

  • 嵌套映射

如果一个 DTO 中的值都是从一个对象中的多个嵌套对象映射时,如果不想一个个写映射,目标可以用 . 表示,如:

@Mapping(source = "role.roleName", target = "roleName")
  • 自定义映射

当我们映射 DTO 的时候,如果某些参数的值 MapStruct 的映射配置不能满足要求,可以使用自定义方法,例如我们对手机号字段借助工具类进行加密后返回:

@Mapping(target = "phoneNum", expression = "java(cn.van.spring.copy.mapstruct.util.Base64Util.encode(userInfoDO.getPhoneNum()))")
  • 完整代码如下:
@Mappings({
@Mapping(source = "id", target = "userId"),
// 自定义转换时间格式
@Mapping(source = "gmtBroth", target = "brothStr", dateFormat = "yyyy-MM-dd",defaultValue = "-"),
// 嵌套映射
@Mapping(source = "role.roleName", target = "roleName"),
// 忽略不映射的字段
@Mapping(target = "password", ignore = true),
// 自定义映射
@Mapping(target = "phoneNum", expression = "java(cn.van.spring.copy.mapstruct.util.Base64Util.encode(userInfoDO.getPhoneNum()))"),
})
UserInfoDTO doToDTO(UserInfoDO userInfoDO);

3.3 多参数映射

MapStruct 可以将几种类型的对象映射为另外一种类型,比如将多个 DO 对象转换为一个 DTO

@Mappings({
@Mapping(source = "userInfoDO.id", target = "userId"),
@Mapping(source = "userInfoDO.gmtBroth", target = "brothStr", dateFormat = "yyyy-MM-dd",defaultValue = "-"),
@Mapping(source = "userInfoDO.role.roleName", target = "roleName"),
// 忽略不映射的字段
@Mapping(target = "password", ignore = true),
// 自定义映射
@Mapping(target = "phoneNum", expression = "java(cn.van.spring.copy.mapstruct.util.Base64Util.encode(userInfoDO.getPhoneNum()))"),
@Mapping(source = "userExtInfoDO.favorite", target = "favorite"),
})
UserInfoDTO doToDtoMulti(UserInfoDO userInfoDO, UserExtInfoDO userExtInfoDO);

这样,我们就可以把 UserInfoDOUserExtInfoDO 映射为 UserInfoDTO

3.4 集合映射

属性映射关系基于一对一的映射关系。

List<UserInfoDTO> doSToDTOS(List<UserInfoDO> userInfoDOS);

3.5 映射到现有实例

上面都是映射并生成一个新的实例,如果是想映射到已有的现有实例呢?

我们只需用 @MappingTarget 修饰。

3.6 注入 Spring

上面的示例调用时都是手动创建了一个 MapstructConvert 实例,

现在都是 Spring 的生态,MapStruct 也可以通过 Spring 注入

@Mapper(componentModel = "spring")
public interface SpringMapstructConvert { /**
* 一对一映射
* @param userInfoDO
* @return
*/
@Mappings({
@Mapping(source = "id", target = "userId"),
// 自定义转换时间格式,如果为空,给予默认值 "-"
@Mapping(source = "gmtBroth", target = "brothStr", dateFormat = "yyyy-MM-dd",defaultValue = "-"),
// 嵌套映射
@Mapping(source = "role.roleName", target = "roleName"),
// 忽略不映射的字段
@Mapping(target = "password", ignore = true),
// 自定义映射
@Mapping(target = "phoneNum", expression = "java(cn.van.spring.copy.mapstruct.util.Base64Util.encode(userInfoDO.getPhoneNum()))"),
})
UserInfoDTO doToDTO(UserInfoDO userInfoDO);
}

相较于前者:干掉了初始化的 INSTANCE@Mapper 注解加入了 componentModel = "spring"

注意:默认是以覆盖原有值的方式映射的,如果要保留原有的值,使用 ignore 忽略字段即可。

四、总结

  • 与手工编写映射代码相比

MapStruct通过生成繁琐且易于编写的代码来节省时间。遵循约定优于配置方法,MapStruct使用合理的默认值,但在配置或实现特殊行为时

最新文章

  1. Java中的URL类
  2. C# 实现 Excel文件的数据导入
  3. circular_buffer
  4. 各种数据库分页sql
  5. C++C++中构造函数与析构函数的调用顺序
  6. 【转载】eclipse调试arm裸机程序
  7. sort方法的使用、随机数的产生
  8. unity3d游戏无法部署到windows phone8手机上的解决方法
  9. c - 统计字符串&quot;字母,空格,数字,其他字符&quot;的个数和行数.
  10. 手工制作OTG连接线 让小白实现OTG功能
  11. Java使用反射机制优化工厂方法
  12. 10- python 网络爬虫分析
  13. android-自定义广告轮播Banner(无限循环实现)
  14. easyui&amp;8Jquery ztree树插件
  15. javascript设计模式——迭代器模式
  16. 发放春节福利,ASP.NET Core断点续传
  17. eclispe安装tomcate没有srver解决
  18. python爬虫——对爬到的数据进行清洗的一些姿势(5)
  19. 服务器&amp;linux
  20. LR和SVM的区别

热门文章

  1. &lt;转&gt;C++继承中虚函数的使用
  2. [BUUCTF]PWN17——[HarekazeCTF2019]baby_rop
  3. C51单片机中断实验
  4. CF977B Two-gram 题解
  5. LuoguP4419 [COCI2017-2018#1] Cezar 题解
  6. 【LeetCode】558. Quad Tree Intersection 解题报告(Python)
  7. 1083 - Histogram
  8. 1145 - Dice (I)
  9. anaconda 命令小览
  10. TSS任务状态段