高效、优雅的对象copy之MapStruct入门到精通,实战踩坑版

news2024/11/24 9:17:05

一、前言

大家在开发中,最让人头疼的就是:对象之间的拷贝,前端的VO和数据库的Entity不一致!

性能最好的就是手动set,主要是枯燥且无技术含量,不仅耗费大量时间而且很容易出错;

所以我们要成为优秀的程序员,要多借助轮子,开发效率事半功倍,开发技能也是增长不少!

如果系统性能没有要求,怎么实现都是好的,但是我们要有追求哈,追求高质量!

每个东西都有存在的价值,不要捧一踩一哈!

二、MapStruct简介

MapStruct是基于JSR 269的Java注释处理器,用于生成类型安全的 Bean 映射类。

您所要做的就是定义一个映射器接口,该接口声明任何所需的映射方法。在编译过程中,MapStruct将生成此接口的实现。此实现使用纯 Java 方法调用在源对象和目标对象之间进行映射,即无反射或类似内容。

与手动编写映射代码相比,MapStruct通过生成繁琐且容易出错的代码来节省时间。遵循配置方法的约定,MapStruct使用合理的默认值,但在配置或实现特殊行为时会步入歧途。

在这里插入图片描述

三、优势

与动态映射框架相比,MapStruct具有以下优点:

  • 通过使用普通方法调用而不是反射快速执行

  • 编译时类型安全:只能映射彼此映射的对象和属性,不会意外地将订单实体映射到客户 DTO 等。

  • 在构建时清除错误报告,如果

    • 映射不完整(并非所有目标属性都已映射)

    • 映射不正确(找不到正确的映射方法或类型转换)

性能图大家可以看一下:
在这里插入图片描述

四、整合实战

0. 使用

@Mapper将接口标记为映射接口
对于源对象和目标对象中具有不同名称的属性,可以使用注释来配置名称:@Mapping
按照约定,接口声明一个成员Mappers INSTANCE,为客户端提供对映射器实现的访问。
下面我们来具体使用!

1. 导入依赖

这里使用最新的,如果引入了lombok可能会有问题,就是他们俩都是在编译期运行的,mapstruct如果比lombok先执行,就会找不到get、set方法,所以会有问题,官网已经有了解决方案!现在是启动不会报错!

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.5.3.Final</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
</dependency>

2. 错误总结

  1. 不会自动生成impl实现类?

我们需要加上依赖:

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.5.3.Final</version>
</dependency>
  1. 重新启动就会出现和lombok的冲突问题:
java: No property named "name" exists in source parameter(s).
 Type "UserVO" has no properties.

官网解决文章地址

<build>
  <pluginManagement>
      <plugins>
          <plugin>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-compiler-plugin</artifactId>
              <version>3.8.1</version>
              <configuration>
                  <source>1.8</source>
                  <target>1.8</target>
                  <annotationProcessorPaths>
                      <path>
                          <groupId>org.mapstruct</groupId>
                          <artifactId>mapstruct-processor</artifactId>
                          <version>1.5.3.Final</version>
                      </path>
                      <path>
                          <groupId>org.projectlombok</groupId>
                          <artifactId>lombok</artifactId>
                          <version>1.18.24</version>
                      </path>
                      <path>
                          <groupId>org.projectlombok</groupId>
                          <artifactId>lombok-mapstruct-binding</artifactId>
                          <version>0.2.0</version>
                      </path>
                  </annotationProcessorPaths>
              </configuration>
          </plugin>
      </plugins>
  </pluginManagement>
</build>

3. 常用实战1

用户表:

@Data
public class User {
    
    private Integer id;
    
    private String username;
    
    private Integer age;
}

前端用户VO:

@Data
public class UserVO {

    private Integer id;

    private String name;

    private Integer age;
}

我们创建接口进行两个对象之间的映射:

import com.example.demo.mapstruct.entity.User;
import com.example.demo.mapstruct.entity.UserVO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

/**
 * @author wangzhenjun
 * @date 2023/1/28 16:05
 */
@Mapper
public interface UserMapper {

    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);

    @Mapping(source ="name",target = "username")
    User userVOToUser(UserVO userVO);

}

属性多了可以嵌套:

@Mappings({
        @Mapping(source ="name",target = "username"),
        @Mapping(source ="name1",target = "username1")
})

也可以:

@Mapping(source ="name",target = "username")
@Mapping(source ="name1",target = "username1")

编写测试类:

@SpringBootTest
class DemoApplicationTests {

    @Test
    void demoMapstruct(){
        UserVO userVO = new UserVO(1,"小红",18);
        User user = UserMapper.INSTANCE.userVOToUser(userVO);
        System.out.println(user);

    }

}

在这里插入图片描述
我们看到拷贝没有任何问题!

我们看看是怎么实现的:

mapstruct会在编译期自动生成实现类去帮助我们去赋值,不指定位默认策略,名称一致进行copy!
不一致可以按上面的进行指定,不指定则不会有set方法!

在这里插入图片描述

4. 常用实战2

下面进行多个源参数的映射方法演示:

我们把user类加上一个字段:

private BigDecimal score;

新增加一个Score类:

@Data
@AllArgsConstructor
public class Score {

    private Integer studentId;

    private BigDecimal score;
}

调整上面的UserMapper接口:

@Mappings({
       @Mapping(source ="userVO.name",target = "username"),
       @Mapping(source ="score.score",target = "score")
})
User userVOToUser(UserVO userVO, Score score);

测试代码:

UserVO userVO = new UserVO(1,"小红",18);
Score score = new Score(1,new BigDecimal(100));
User user = UserMapper.INSTANCE.userVOToUser(userVO,score);
System.out.println(user);

结果显示正常:

在这里插入图片描述

5. 常用实战3

我们在来看一个企业级能够用得上的,就是自定义方法,然后进行赋值:

场景:一个商品有长宽高,我们把长宽高从cm变为m!

还有很多String转Integer、Float等等,都是按照下面这种自定义方法去实现!

VO和对象都是一样的哈!

@Data
@AllArgsConstructor
public class ProductVO {

    private Integer id;

    private String name;

    private BigDecimal length;

    private BigDecimal width;

    private BigDecimal high;

}

看清楚,别导错包了!
qualifiedByName:指定自定义方法的名称
@Named("cmToM"):起别名,不使用找不到方法

可以写一起,也可以整一个工具类里写方法,在这里进行引用!
如果是使用spring,我们可以把接口作为bean进行注入调用(推荐
加上参数即可开启:
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)

/**
 * @author wangzhenjun
 * @date 2023/1/28 17:13
 */
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface ProductMapper {

    @Mapping(source = "length",target = "length",qualifiedByName = "cmToM")
    @Mapping(source = "width",target = "width",qualifiedByName = "cmToM")
    @Mapping(source = "high",target = "high",qualifiedByName = "cmToM")
    Product productVOToPrduct(ProductVO productVO);

    @Named("cmToM")
    default BigDecimal cmToM (BigDecimal oldValue){
        if (oldValue == null) {
            return BigDecimal.ZERO;
        }
        return oldValue.divide(new BigDecimal("100"));
    }
}

测试:

@SpringBootTest
class DemoApplicationTests {

    @Autowired
    private ProductMapper productMapper;

    @Test
    void demoMapstruct(){

        ProductVO productVO = new ProductVO(1,"美丽家园地板",new BigDecimal(100),new BigDecimal(50),new BigDecimal(8));
        Product product = productMapper.productVOToProduct(productVO);

        System.out.println(product);
    }

}

完美转化!

在这里插入图片描述

6. 常用实战4

在实战一个LocalDateTime、String相互转化的,后面大家可以去官网文档去找你需要的:

在刚刚的商品类加个字段:

private String createdAt;

VO里也加上一个:

private LocalDateTime createdAt;

编写个转化类:
这里交给spring管理了,因为ProductMapper也交给spring管理,不加的话会找不到此类!

@Component
public class LocalDateTimeMapper {

    public String asString(LocalDateTime date) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        return date != null ? date.format(formatter): null;
    }

    public LocalDateTime asLocalDateTime(String date) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        return date != null ? LocalDateTime.parse(date,formatter) : null;
    }
}

ProductMapper修改一下:

uses = LocalDateTimeMapper.class使用咱们上面写的类即可!

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING,uses = LocalDateTimeMapper.class)
public interface ProductMapper {

    @Mapping(source = "length",target = "length",qualifiedByName = "cmToM")
    @Mapping(source = "width",target = "width",qualifiedByName = "cmToM")
    @Mapping(source = "high",target = "high",qualifiedByName = "cmToM")
    Product productVOToProduct(ProductVO productVO);

    @Named("cmToM")
    default BigDecimal cmToM (BigDecimal oldValue){
        if (oldValue == null) {
            return BigDecimal.ZERO;
        }
        return oldValue.divide(new BigDecimal("100"));
    }
}

测试一下:

ProductVO productVO = new ProductVO(1,"美丽家园地板",
new BigDecimal(100),new BigDecimal(50),
new BigDecimal(8), LocalDateTime.now());
Product product = productMapper.productVOToProduct(productVO);
System.out.println(product);

完美转化:

在这里插入图片描述

六、总结

通过简介到实战,这时咱们就是优雅的程序员了!

更多的例子可以去官网进行查看:

mapstruct开发文档

写作不易,大家给点支持,你的支持是我写作的动力哈!

对你有帮助,还请不要吝啬你的发财小手点点关注哈!

关注小编的微信公众号,一起交流学习!文章首发看哦!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/185300.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

jsp 新房销售竞买管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 jsp 新房销售竞买管理系统 是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统采用web模式开发&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开发&…

OJ万题详解––赦免战俘(C++详解)

题目 题目背景 借助反作弊系统&#xff0c;一些在月赛有抄袭作弊行为的选手被抓出来了&#xff01; 题目描述 现有 名作弊者站成一个正方形方阵等候 kkksc03 的发落。kkksc03 决定赦免一些作弊者。他将正方形矩阵均分为 4 个更小的正方形矩阵&#xff0c;每个更小的矩阵的边长…

VSCode调试Python文件并指定虚拟环境 附调试说明

记录在使用VSCode调试py文件时遇到的一些问题&#xff1a;1.启用Debug模式进行调试2.调试带参数的python文件3. 调试时指定服务器虚拟环境4. 相对路径出错5 continue&#xff08;继续&#xff09;、step over&#xff08;单步跳过&#xff09;、step in&#xff08;单步调试&am…

【Python学习笔记】2. Python3 环境搭建

前言 将向大家介绍如何在本地搭建 Python3 开发环境。 Python3 可应用于多平台包括 Windows、Linux 和 Mac OS X。 Python3 环境搭建 本节我们将向大家介绍如何在本地搭建 Python3 开发环境。 Python3 可应用于多平台包括 Windows、Linux 和 Mac OS X。 Unix (Solaris, Li…

我理解的测试开发与实践总结——新人篇

我理解的测试开发与实践总结——新人篇 目录导读 一、我理解的测试开发 二、测试和开发、产品的关系 三、测试开发需要具备的技能 四、我们在测试过程中需要做到什么程度 五、我们需要具备的素质 六、测试工作流程及关注点有哪些 七、平时常用的一些小工具和测试技巧 写…

windows下编译mingw版本的glew库

glew下载 https://glew.sourceforge.net/index.html windows二进制版下来是MSVC版的库文件&#xff0c;.lib文件。而gcc并不能使用&#xff0c;需要重新进行编译。 下载源码版的glew文件&#xff0c;解压&#xff0c;进入到源码目录&#xff0c;打开cmd 第一步&#xff1a;编译…

【OpenCV】 模版匹配 | 霍夫变换 | 霍夫线、圆检测

Ⅰ. 模版匹配和霍夫变换 0x00 模板匹配 原理 所谓的模板匹配&#xff0c;就是在给定的图片中查找和模板最相似的区域&#xff0c;该算法的输入包括模板和图片&#xff0c;整个任务的思路就是按照滑窗的思路不断的移动模板图片&#xff0c;计算其与图像中对应区域的匹配度&…

春节要闻回顾 | 美SEC被列入Genesis债权人名单;亚马逊或将推出专注于游戏的NFT项目...

春节期间区块链行业要闻回顾&#xff1a;产业方面&#xff0c;达沃斯央行数字货币小组认可CBDC是央行货币的未来&#xff0c;但目前部署存在困难&#xff0c;亚马逊或将推出专注于游戏的NFT项目&#xff1b;政策方面&#xff0c;拜登政府将在未来几个月公布数字资产优先事项&am…

数据结构——顺序表(三)

数据结构 文章目录数据结构一、什么是顺序表二、顺序表的创建1.静态顺序表2.动态数据表三、顺序表的初始化、销毁四、顺序表的插入1.尾插2.头插3.任意插入总结一、什么是顺序表 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构&#xff0c;一般情况下采用数组…

Python实例方法、静态方法和类方法详解

和类属性一样&#xff0c;类方法也可以进行更细致的划分&#xff0c;具体可分为类方法、实例方法和静态方法。和类属性的分类不同&#xff0c;对于初学者来说&#xff0c;区分这 3 种类方法是非常简单的&#xff0c;即采用 classmethod 修饰的方法为类方法&#xff1b;采用 sta…

FreeRTOS消息队列

消息队列是一种常用于任务间通信的数据结构。通过消息队列服务&#xff0c;任务或 中断服务例程可以将一条或多条消息放入消息队列中&#xff0c;同样&#xff0c;一个或多个任务可 以从消息队列中获得消息。本章将向大家介绍 FreeRTOS 的消息队列&#xff0c;通过本章 的学习&…

238. 除自身以外数组的乘积

【答案解析】&#xff1a;暴力不考虑其他的因素的话&#xff0c;将所有数据乘积起来&#xff0c;然后遍历数组除以当前位置数据即可。更优解法&#xff1a;将乘积分为两次进行&#xff0c;第一次先将每个位置左边的数据乘积计算出来放到返回数组中&#xff0c;后边第二次循环 将…

【Java面试(二)】冒泡排序的实现及优化

文章目录前言冒泡排序初步实现冒泡排序_优化_减少比较次数冒泡排序_优化_减少冒泡次数冒泡排序_优化_进一步优化比较次数总结前言 今天我们来学习与排序相关的面试题&#xff0c;首先我们先来学习冒泡排序&#xff0c;那什么是冒泡排序呢&#xff0c;它的关键在于数组中相邻元素…

变聪明的方法就是学习,每个人最终都会与生活和解,连村西透仿佛也捡回了昔日的勇气。

文章目录❤️‍&#x1f525; 序❤️‍&#x1f525; 往事如风迹难寻 - 成为创作者的契机❤️‍&#x1f525; 新星计划遇善者 - 出道即是巅峰MVP❤️‍&#x1f525; 知行合一心依旧 - 初心不改坚持创作❤️‍&#x1f525; 知易行难搞规划 - 这是不能说的秘密❤️‍&#x1f…

中国为何就不能有自己的豪华MPV?

文|智能相对论作者| 陈明涛是时候重新认识海外汽车品牌和国产汽车品牌的MPV了。去年&#xff0c;曾有雷克萨斯LM在碰撞后出现全车车门无法打开&#xff0c;之后车辆起火酿成悲剧&#xff0c;引发了全网对MPV碰撞安全的高度关注。前段时间丰田埃尔法再登热搜&#xff0c;这次不是…

2023最新Python国内镜像源,亲测可用

1、镜像源 pip包管理工具可以下载第三方库到本地使用&#xff0c;第三方库的来源地址称之为镜像源&#xff0c;镜像源中存放了大量的开源库供我们下载使用。pip的默认镜像源地址在国外&#xff0c;下载很慢&#xff0c;本文收集了当前国内常用的镜像源&#xff0c;速率由快到慢…

Java多线程 - 定时器-并发与并行-线程生命周期

文章目录多线程补充定时器并发和并行线程的生命周期多线程补充 定时器 定时器介绍: 定时器是一种控制任务延时调用&#xff0c;或者周期调用的技术。 作用&#xff1a;闹钟、定时邮件发送。 定时器实现方式: 方式一&#xff1a;Timer 方式二&#xff1a; ScheduledExecutorSe…

java程序报错后的排错思路

目前总结出来三个字&#xff1a;看日志&#xff01; 而且是从左到右一个单词一个单词的看。   举个例子&#xff1a;   Spring框架下的一个Demo&#xff0c;启动时报出了以下错误。 一、看异常类型   首先,能看到异常是从引入的SpringFramework依赖中报出来的&#xf…

[数据结构基础]排序算法第三弹 -- 快速排序

目录 一. 快速排序的基本思想 二. 快速排序的递归实现 2.1 单趟快速排序的实现 2.1.1 Hoare法实现单趟快排 2.1.2 挖坑法实现单趟快排 2.1.3 前后指针法实现单趟快排 2.2 递归快排的整体实现 三. 快速排序的时间复杂度分析 四. 快速排序的非递归实现 4.1 快速排序非递…

Promise详解与手写实现

Promise详解与手写实现Promise1、Promise介绍与基本使用1.1 Promise概述1.2 Promise的作用1.3 Promise的使用2、Promise API3、Promise关键问题4、Promise自定义封装5、async与await5.1. mdn文档5.2.async函数5.3.await表达式5.4.注意Promise 1、Promise介绍与基本使用 1.1 P…