Java MapStruct解了对象映射的毒
作者:JaJian 发布时间:2022-08-20 11:37:12
前言
MVC模式是目前主流项目的标准开发模式,这种模式下框架的分层结构清晰,主要分为Controller,Service,Dao。分层的结构下,各层之间的数据传输要求就会存在差异,我们不能用一个对象来贯穿3层,这样不符合开发规范且不够灵活。
我们常常会遇到层级之间字段格式需求不一致的情况,例如数据库中某个字段是datetime
日期格式,这个时间戳在数据库中的存储值为2020-11-06 23:59:59.999999
,但是传递给前端的时候要求接口返回yyyy-MM-dd
的格式,或者有些数据在数据库中是逗号拼接的String类型,但是前端需要的是切割后的List类型等等。
所以我们提出了层级间的对象模型,就是我们常见的VO,DTO,DO,PO等等。这种区分层级对象模型的方式虽然清晰化了我们各层级间的对象传递,但是对象模型间的相互转换和值拷贝确是让人感觉很麻烦,拷贝来拷贝去,来来回回,过程重复乏味,编写此类映射代码是一项繁琐且容易出错的任务。
最简单粗糙的拷贝方法就是不断的new对象然后对象间的 setter 和 getter,这种方式应对字段属性少的还可以,如果属性字段很多那么大段的set,get的代码就显得很不雅美。因此需要借助对象拷贝工具,目前市场上的也蛮多的像BeanCopy,Dozer等等,但是这些我感觉都不够好,今天我推荐一个实体映射工具就是 MapStruct。
介绍
MapStruct的官网地址是 https://mapstruct.org/MapStruct,是一个快速安全的bean 映射代码生成器,只需要通过简单的注解就可以实现对象间的属性转换,是一款 Apache LICENSE 2.0 授权的开源产品,Github的源码地址是 https://github.com/mapstruct。
通过官网的三连问(What,Why,How)我们可以大概的了解到 MapStruct 的作用,它的优势以及它是如何实现的。
从上面的三连问中我们可以得到如下信息:
基于约定优于配置的方法 MapStruct 极大地简化了 Java bean 类型之间的映射的实现,通过简单的注解就可以工作。生成的映射代码使用普通的方法调用而不是反射,因此速度快,类型安全且易于理解。
在编译时生成 Bean 映射 与其他映射框架相比,MapStruct 在编译时生成 Bean 映射,这样可以确保高性能,而且开发人员可以快速的得到反馈和彻底的错误检查。
一个注释处理器 MapStruct 是一个注释处理器,已插入 Java 编译器,可用于命令行构建(Maven,Gradle等),也可用于您首选的IDE中(IDEA,Eclipse等)。
代码编写
MapStruct 需要 Java 1.8或更高版本。对于Maven-based 的项目,在pom 文件中添加如下依赖即可
<!-- 指定版本-->
<properties>
<org.mapstruct.version>1.4.1.Final</org.mapstruct.version>
</properties>
<!-- 添加依赖 -->
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
</dependencies>
基本的依赖引入后就可以编写代码了,简单的定义一个映射类,为了与 Mybatis中的 mapper 接口区分,我们可以取名为 xxObjectConverter
。
例如汽车对象的映射类名为 CarObjectConverter
,我们有两个对象模型 DO 和 DTO,它们内部的属性字段如下:
数据库对应的持久化对象模型 CarDo
public class Car {
@ApiModelProperty(value = "主键id")
private Long id;
@ApiModelProperty(value = "制造商")
private String manufacturers;
@ApiModelProperty(value = "销售渠道")
private String saleChannel;
@ApiModelProperty(value = "生产日期")
private Date productionDate;
...
}
层级间传输的对象模型 CarDto
public class CarDto {
@ApiModelProperty(value = "主键id")
private Long id;
@ApiModelProperty(value = "制造商")
private String maker;
@ApiModelProperty(value = "销售渠道")
private List<Integer> saleChannel;
@ApiModelProperty(value = "生产日期")
private Date productionDate;
...
}
再编写具体的 MapStruct 对象映射器
@Mapper
public interface CarObjectConverter{
CarObjectConverter INSTANCE = Mappers.getMapper(CarObjectConverter.class);
@Mapping(target = "maker", source = "manufacturers")
CarDto carToCarDto(Car car);
}
对于字段名相同的可以不用额外的指定映射规则,但是字段名不同的属性则需要指出字段的映射规则,如上我们持久层 DO 的制造商的字段名是manufacturers
而层级间传输的DTO模型中则是maker
,我们就需要在映射方法上通过@Mapping
注解指出映射规则,我个人习惯是喜欢将target
写在前面,source
写在后面,这样是与映射对象的位置保持一致,差异字段多的时候方便对比且不易混淆。
开发过程中还会经常遇到一些日期格式的转换,就如开篇时说的那种,这时我们也可以指定日期的映射规则
@Mapper
public interface CarObjectConverter{
CarObjectConverter INSTANCE = Mappers.getMapper(CarObjectConverter.class);
@Mapping(target = "maker", source = "manufacturers")
@Mapping(target = "productionDate", dateFormat = "yyyy-MM-dd", source = "productionDate")
CarDto carToCarDto(Car car);
}
这些都还是一些简单的字段的映射,但有时候我们两个对象模型间的字段类型不一致,如上汽车的销售渠道字段saleChannel
,这个在数据库中是字符串逗号拼接的值1,2,3
,而我们传递出去的需要是 List 的 Integer 类型,这种复杂的如何映射呢?
也是有方法的,我们先编写一个将字符串逗号分隔然后转成 List 的工具方法,如下
public class CollectionUtils {
public static List<Integer> list2String(String str) {
if (StringUtils.isNoneBlank(str)) {
return Arrays.asList(str.split(",")).stream().map(s -> Integer.valueOf(s.trim())).collect(Collectors.toList());
}
return null;
}
}
然后在映射Mapping中使用表达式即可
@Mapper
public interface CarObjectConverter {
CarObjectConverter INSTANCE = Mappers.getMapper(CarObjectConverter.class);
@Mapping(target = "maker", source = "manufacturers")
@Mapping(target = "productionDate", dateFormat = "yyyy-MM-dd", source = "productionDate")
@Mapping(target = "saleChannel", expression = "java(com.jiajian.demo.utils.CollectionUtils.list2String(car.getSaleChannel()))")
CarDto carToCarDto(Car car);
}
这样就完成了所有字段的映射工作,我们在需要对象模型转换的地方按照如下方式调用即可
CarDto carDto = CarObjectConverter.INSTANCE.carToCarDto(car);
这种是单体对象之间的 Copy 很多时候我们需要 List 对象模型间的转换,只需要再写一个方法carToCarDtos
即可
@Mapper
public interface CarObjectConverter{
CarObjectConverter INSTANCE = Mappers.getMapper(CarObjectConverter.class);
@Mapping(target = "maker", source = "manufacturers")
@Mapping(target = "productionDate", dateFormat = "yyyy-MM-dd", source = "productionDate")
@Mapping(target ="saleChannel", expression = "java(com.jiajian.demo.utils.CollectionUtils.list2String(car.getSaleChannel()))")
CarDto carToCarDto(Car car);
List<CarDto> carToCarDtos(List<Car> carList);
}
探个究竟
会不会好奇这是怎么实现的,我们只是创建了一个接口然后在接口方法上加一个注解并在注解里面指定字段的映射规则就可以实现对象属性间的拷贝,这是怎么做到的呢?
我们这里通过 MapStruct 创建的只是一个接口,要实现具体的功能接口必有实现。
MapStruct 会在我们代码编译的时候为我们创建一个实现类,而这个实现类里面通过字段的setter, getter方法来实现字段的赋值,从而实现对象的映射。
这里需要注意一点:如果你修改了任一映射对象,记得需要先执行mvn clean再启动项目,否则调试的时候会报错。
结尾
MapStrut 的功能远不至于上面介绍的这些,我只是挑出几个常用的语法进行示例讲解,如果读者感兴趣想深入的了解更多可以参考官方的参考文档
遇见 MapStruct 后我就开始在项目中抛弃掉了原来的那些 BeanCopyUtils 的工具,相对而言 MapStruct 确实更简洁且易使用而且定制功能也很强。
从编译文件可以看出 MapStruct 是通过setter,getter来实现属性值的拷贝,然后这种方式不是最简单又最安全高效的吗?只是 MapStruct 更好的帮助我们实现了,避免了项目中冗余的重复代码,大道至简。
来源:https://www.cnblogs.com/jajian/p/13937897.html


猜你喜欢
- 前言:时间过得真是快,现在已经是2022年了。作为开发来说,时间处理是非常繁琐的。从Java 8开始有了Java 8 T
- 最近.一个朋友跟我说想,我给她弄个闹钟APP软件...功能其实很简单...只需要弄个简单的闹钟.自己设计设计时间.然后时间到了的时候,闹铃放
- 本文实例讲述了C#调用存储过程的方法。分享给大家供大家参考,具体如下:CREATE PROCEDURE [dbo].[GetNameById
- 详解Android使用@hide的API的方法今天早上想修改MediaPlaybackService.Java(/packages/apps
- 前言定义一个工厂类,他可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类在简单工厂模式中用于被创建实例的方法通常为静态(s
- BottomBarBottomBar是Github上的一个开源框架,因为从1.3.3开始不支持fragments了,要自己配置,弄了很久,不
- 运行原理1、不同线程中所包含的栈帧是不允许存在相互引用的。2、如果当前方法调用了其他方法,方法返回之际,当前栈帧会传回此方法的执行结果给当前
- 前言在机器学习中,卷积神经网络是一种深度前馈人工神经网络,已成功地应用于图像识别。目前,很多的车牌识号识别,人脸识别等都采用卷积神经网络,可
- 软硬件环境Macbook Pro MGX 72Android studio 2.1.2Android 5.1.1前言上一篇介绍了如何获取et
- 本文为大家分享了Unity实现粒子光效导出成png序列帧的具体代码,供大家参考,具体内容如下这个功能并不是很实用,不过美术同学有这样的需求,
- 参数说明CultureInfo.CurrentCulture获取当前线程的区域信息中,包括DateTimeFormat 日期显示格式(日期分
- 开发一个需要常住后台的App其实是一件非常头疼的事情,不仅要应对国内各大厂商的ROM,还需要应对各类的安全管家...虽然不断的研究各式各样的
- 概述非对称加密算法与对称加密算法的主要差别在于非对称加密算法用于加密和解密的密钥不相同,非对称加密算法密钥分为公钥和私钥,公钥加密只能用私钥
- 传值就是将实参的值传到所调用的函数里面,实参的值并没有发生变化,默认传值的有int型,浮点型,bo
- 引言Spring Boot的一个便捷功能是外部化配置,可以轻松访问属性文件中定义的属性。本文将详细介绍@ConfigurationPrope
- 目录方案一,更改启动项方案二,禁用检查应用程序的CAS发布者策略方案一,更改启动项出问题应用的启动项是使用的默认设置,查看App.g.cs文
- 效果图简单的效果图(使用开源库)[FlowLayout](“ https://github.com/hongyangAndroid/Flow
- 1.实现一个ItsClient 客户端用来实例化调用验证功能public class ItsClient {private static f
- 错误示例,同一个类中使用异步方法:package com.xqnode.learning.controller;import com.fas
- 简介:本篇博客主要包括recyclerview添加多种布局以及添加头布局和尾布局,还有item点击事件思路:主要重写Recyclerview