前言

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 的功能远不至于上面介绍的这些,我只是挑出几个常用的语法进行示例讲解,如果读者感兴趣想深入的了解更多可以参考官方的参考文档,Reference Guide

遇见 MapStruct 后我就开始在项目中抛弃掉了原来的那些 BeanCopyUtils 的工具,相对而言 MapStruct 确实更简洁且易使用而且定制功能也很强。

从编译文件可以看出 MapStruct 是通过setter,getter来实现属性值的拷贝,然后这种方式不是最简单又最安全高效的吗?只是 MapStruct 更好的帮助我们实现了,避免了项目中冗余的重复代码,大道至简。

MapStruct 解了对象映射的毒的更多相关文章

  1. 从壹开始前后端分离【 .NET Core2.0 +Vue2.0 】框架之十三 || DTOs 对象映射使用,项目部署Windows+Linux完整版

    更新 很多小伙伴在用 IIS 发布的时候,总是会有一些问题,文章下边 #autoid-6-0-0 我也简单的动图展示了,如何 publish 到 IIS 的过程,如果你能看懂,却发现自己的项目有问题的 ...

  2. Struts功能详解——ActionMapping对象

    Struts功能详解——ActionMapping对象 ActionMapping描述了struts中用户请求路径和Action的映射关系,在struts中每个ActionMapping都是通过pat ...

  3. .NET平台开源项目速览(14)最快的对象映射组件Tiny Mapper

    好久没有写文章,工作甚忙,但每日还是关注.NET领域的开源项目.五一休息,放松了一下之后,今天就给大家介绍一个轻量级的对象映射工具Tiny Mapper:号称是.NET平台最快的对象映射组件.那就一起 ...

  4. 对象映射工具AutoMapper介绍

    AutoMapper是用来解决对象之间映射转换的类库.对于我们开发人员来说,写对象之间互相转换的代码是一件极其浪费生命的事情,AutoMapper能够帮助我们节省不少时间. 一. AutoMapper ...

  5. EF架构~AutoMapper对象映射工具简化了实体赋值的过程

    回到目录 AutoMapper是一个.NET的对象映射工具,一般地,我们进行面向服务的开发时,都会涉及到DTO的概念,即数据传输对象,而为了减少系统的负载,一般我们不会把整个表的字段作为传输的数据,而 ...

  6. MojoDatabase 源码学习之对象映射

    Mojo-database是我个人比较喜欢多开源项目,下文是该项目打介绍和地址: mojo-database 简介: MojoDatabase is an ActiveRecord-like ORM ...

  7. php设计模式 数据对象映射模式

    数据对象映射模式,是将对象和数据存储映射起来,对一个对象的操作会映射为对数据存储的操作. 在代码中实现数据对象映射模式,实现一个ORM类,将复杂的sql语句映射成对象属性的操作.对象关系映射(Obje ...

  8. c#注册表对象映射

    用于快捷保存与读取注册表,为对应的对象 示例 [RegistryRoot(Name = "superAcxxxxx")] public class Abc : IRegistry ...

  9. ASP.NET MVC 模型和数据对象映射实践

    在使用 MVC 开发项目的过程中遇到了个问题,就是模型和数据实体之间的如何快捷的转换?是不是可以像 Entity Framework 的那样 EntityTypeConfiguration,或者只需要 ...

随机推荐

  1. Python中matplotlib.pyplot.imshow画灰度图的多种方法

    转载:https://www.jianshu.com/p/8f96318a153f matplotlib库的教程和使用方法此处就不累赘了,网上有十分多优秀的教程资源.此处直接上代码: def demo ...

  2. [POI2009]ARC-Architects

    [POI2009]ARC-Architects 题意: 给定一个序列,从中挑选k个数,满足下标单调递增,并且字典序最小: 思路: 由于字典序最小,所以考虑贪心,即前面的数尽可能大,所以用单调队列维护最 ...

  3. spring-boot-route(十一)数据库配置信息加密

    Spring Boot最大的特点就是自动配置了,大大的减少了传统Spring框架的繁琐配置,通过几行简单的配置就可以完成其他组件的接入.比如你想要连接mysql数据库,只需要的配置文件里面加入mysq ...

  4. Windows10系统下wsappx占用CPU资源过高?wsappx是什么?如何关闭wsappx进程?

    在Windows10系统开机的时候,wsappx进程占用的CPU资源非常高,导致电脑运行速度缓慢,那么我们如何关闭wsappx进程,让电脑加快运行速度呢?下面就一起来看一下操作的方法吧. [现象] 1 ...

  5. Python日志采集(详细)

    通常在前期调试代码的时候,我们会使用print在IDE控制台打印一些信息,判断运行情况.但在运行整个自动化测试项目的过程中,通过print打印信息的方式获取运行情况显然行不通. 这时就需要收集日志,每 ...

  6. Jenkins从节点上构建自动化测试项目时报错:java.io.IOException: Unexpected termination of the channel

    在mac电脑上配置了Jenkins从节点,在该从节点上构建app UI 自动化测试项目,运行一些用例后报如下错误: java.io.EOFException at java.io.ObjectInpu ...

  7. element Ui的级联选择器 任意一级选中下拉框自动关闭

    封装成一个子组件 <template> <el-cascader v-model="value" clearable placeholder="请选择& ...

  8. 测试AAA

    程序计数器(线程私有) 程序计数器(Program Counter Register),也有称作为 PC 寄存器.保存的是程序当 前执行的指令的地址(也可以说保存下一条指令的所在存储单元的地址),当 ...

  9. h5的第一份翻译

    <!DOCTYPE html>DOCTYPE DOC文本文档documentTYPE 类型html hyper超,超级的:text文本:markup标记:language语言<htm ...

  10. Springboot+Redis(发布订阅模式)跨多服务器实战

    一:redis中发布订阅功能(http://www.redis.cn/commands.html#pubsub) PSUBSCRIBE pattern [pattern -]:订阅一个或者多个符合pa ...