前言

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. 030 01 Android 零基础入门 01 Java基础语法 03 Java运算符 10 条件运算符

    030 01 Android 零基础入门 01 Java基础语法 03 Java运算符 10 条件运算符 本文知识点:Java中的条件运算符 条件运算符是Java当中唯一一个三目运算符 什么是三目运算 ...

  2. Java知识系统回顾整理01基础01第一个程序03Eclipse下载安装

    Eclipse是最流行的java 集成开发环境IDE(Integrated Development Environment) 下载安装Eclipse两种方式 一.方式1:Eclipse官网下载安装 链 ...

  3. 正则表达式查找“不包含XXX字符串”

    使用 当我要找到不包含某些字符串(如test)时, 可以使用 # 独立使用 (?!test). # 加头尾判断 ^((?!test).)*$ 原理 正则表达式的断言功能: (?=pattern) 非获 ...

  4. SpringBoot常见注解

    0.前言 这篇文章介绍的 Spring/SpringBoot 常用注解基本已经涵盖你工作中遇到的大部分常用的场景.对于每一个注解我都说了具体用法,掌握搞懂,使用 SpringBoot 来开发项目基本没 ...

  5. 机器学习 KNN算法原理

    K近邻(K-nearst neighbors,KNN)是一种基本的机器学习算法,所谓k近邻,就是k个最近的邻居的意思,说的是每个样本都可以用它最接近的k个邻居来代表.比如:判断一个人的人品,只需要观察 ...

  6. idea创建servlet工程初体验

    servlet工程创建 前提:创建项目之前需要配置java环境变量 和tomcat配置,配置完成后进入如下操作. tomcat 安装和配置参考 https://www.cnblogs.com/xush ...

  7. xuexi

    1.内存的编址方法就是内存地址与内存单元格一一对应且永久绑定.计算机的cpu只认识内存地址,不关心内存单元格的位置和内容.通过硬件的设计来达到通过内存地址找到内存单元格. 2.内存的编址是以字节为单位 ...

  8. [Leetcode题解]2. 两数相加-链表遍历和重构

    1. 审题leetcode 02 add-two-numbers​ 我们先看一下题目,如下  : 链表的从前往后为数字的低位到高位,模拟加法手算过程,从前往后遍历即可, 注意每个数字0-9,进位要处理 ...

  9. centos8平台使用lsof

    一,lsof的用途 lsof,List Open Files 列出当前系统打开文件的工具. 在linux环境下,任何事物都以文件的形式存在, 所以lsof通过文件不仅仅可以访问常规数据,还可以访问网络 ...

  10. Helium文档7-WebUI自动化-highlight高亮显示元素

    前言 highlight方法是通过红框高亮显示元素,在调试中有很大优势,可以清楚看到定位的元素位置 入参介绍 def highlight(element):   """ ...