代码优化方法不包括 【代码优化】Bean映射之MapStruct

【代码优化】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 类:
用户信息:
@Datapublic 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;}}用户补充信息:
@Datapublic class UserExtInfoDO {private String favorite;public UserExtInfoDO() {}public UserExtInfoDO(String favorite) {this.favorite = favorite;}}角色信息:
@Datapublic 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 类:
@Datapublic 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;}}
  • 添加映射接口
@Mapperpublic interface MapstructConvert {/*** 获取该类自动生成的实现类的实例*/MapstructConvert INSTANCE = Mappers.getMapper(MapstructConvert.class);}
  1. 添加一个 interface 接口,使用 MapStruct@Mapper 注解修饰;
  2. 使用 Mappers 添加一个 INSTANCE 实例(也可以使用 Spring 注入,后面会扩展) 。
3.2 一对一映射
  • 自定义转换时间格式
通过 dateFormat = "xx" 指定映射的日期格式 。
  • 指定默认值
如果该值为空,则使用指定的默认值,如:defaultValue = "https://tazarkount.com/read/-"
  • 忽略不映射的字段
可以通过 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 = "https://tazarkount.com/read/-"),// 嵌套映射@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 = "https://tazarkount.com/read/-"),@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 = "https://tazarkount.com/read/-"),// 嵌套映射@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 忽略字段即可 。
四、总结
  • 与手工编写映射代码相比
【代码优化方法不包括 【代码优化】Bean映射之MapStruct】MapStruct通过生成繁琐且易于编写的代码来节省时间 。遵循约定优于配置方法,MapStruct使用合理的默认值,但在配置或实现特殊行为时
技术交流,欢迎扫一扫!风尘博客
代码优化方法不包括 【代码优化】Bean映射之MapStruct

文章插图