优雅使用 MapStruct 进行类复制

前言

在项目中,常常会遇到从数据库读取数据后不能直接返回给前端展示的情况,因为还需要对字段进行加工,比如去除时间戳记录、隐藏敏感数据等。传统的处理方式是创建一个新类,然后编写大量的 get/set 方法进行赋值,若字段很多,这无疑是一场噩梦,而且还容易出现遗漏的情况。

我们都清楚,随着工程日益成熟,模块划分会越发细致。实体类通常存放在 domain 中,但最好不要让 domain 工程被其他工程依赖。所以,当其他工程需要获取实体类数据时,就需要在各自工程中编写 model。自定义 model 能够根据自身业务需求映射相应的实体属性。如此一来,这个映射工作似乎并不简单。

这个时候,我们可以使用MapStruct
在企业级应用中,经常需要在不同类型的对象(如 DTO 和 DO、VO 和 PO 等)之间进行转换。MapStruct 通过在编译时基于接口定义生成转换代码,大大简化了这个过程。例如,从一个包含用户注册信息的 DTO 转换为一个用于业务逻辑处理的 DO 时,只需要定义一个 MapStruct 接口。

引入依赖

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>17</java.version>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <lombok.version>1.18.34</lombok.version>
    <mapstruct.version>1.6.2</mapstruct.version>
</properties>

<dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.13.2</version>
      <scope>test</scope>
    </dependency>
    
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>${lombok.version}</version>
    </dependency>
    
    <dependency>
      <groupId>org.mapstruct</groupId>
      <artifactId>mapstruct</artifactId>
      <version>${mapstruct.version}</version>
    </dependency>
    <dependency>
      <groupId>org.mapstruct</groupId>
      <artifactId>mapstruct-processor</artifactId>
      <version>${mapstruct.version}</version>
      <scope>provider</scope>
    </dependency>

</dependencies>

org.mapstruct:mapstruct

  • 这是MapStruct的核心库。它包含了用于定义映射接口的注解(如@Mapper@Mapping等)以及在运行时执行映射操作所需的类型和接口。
  • 开发人员在Java代码中使用这些注解来定义对象之间的映射关系。例如,在不同的领域对象(Domain Object)、数据传输对象(Data Transfer Object)、视图对象(View Object)等之间的转换映射。这个库提供了基本的框架,使得可以按照声明式的方式指定对象属性如何从一个对象映射到另一个对象。
  • 当执行映射操作时(例如,通过调用由MapStruct生成的映射器实例的映射方法),这个库中的代码会协调映射过程,根据定义的映射规则进行数据的转换。

org.mapstruct:mapstruct - processor

  • 这个依赖是MapStruct的注解处理器(Annotation Processor)。在Java编译过程中,它会查找带有MapStruct注解(来自org.mapstruct:mapstruct库)的接口或抽象类。
  • 一旦找到这样的接口或抽象类,它会根据定义的映射关系(通过@Mapper@Mapping等注解)生成具体的映射实现类。这个生成过程是在编译时进行的,生成的代码会被编译到最终的字节码中。
  • 例如,如果有一个定义了从SourceObjectTargetObject映射关系的@Mapper接口,mapstruct - processor会生成一个实现该接口的类,这个类包含了将SourceObject的属性值按照指定规则赋给TargetObject属性的具体代码。这里的<scope>provider</scope>表示该依赖是一个提供运行时环境所需的组件,主要用于在编译时提供代码生成功能。

定义实体

定义两个实体,字段上略微有些差别

import java.util.Date;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private long id;
    private String name;
    private int age;
    private String password;
    private Date createTime;
}

import lombok.Data;

@Data
public class UserVO {
    private Long id;
    private String name;
    private Integer age;
    private String code;
    private String hello;
    private String createTime;
}

定义转换的mapper

如果是spring项目,用@Mapper(componentModel = "spring"),生成的实现类上面会自动添加一个@Component注解,可以通过Spring的 @Autowired方式进行注入

package com.zxy.demo;

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

@Mapper
public interface UserMapper {
    public static final UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);

    static String hello(User user) {
        return "hello " + user.getName();
    }

    @Mapping(target = "createTime", source = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
    @Mapping(target = "code", expression="java(\"xx-\" + user.getId())")
    @Mapping(target = "hello", expression = "java(UserMapper.hello(user))")
    UserVO toUserVO(User user);

}

如果所有的字段都一样,用@Mappings({})
不一样的用target+source,需要特殊处理的可以用expression

MapStruct 中,expression是一个强大的功能,用于在对象映射过程中执行自定义的表达式。它允许开发人员在映射规则中使用 Java 表达式来处理复杂的映射逻辑,而不仅仅是简单的属性到属性的映射。

这在源对象和目标对象的属性之间存在复杂关系,或者需要进行额外的计算、逻辑判断等情况时非常有用。

来个单测运行一下

package  com.zxy.demo;

import java.util.Date;

import org.junit.Assert;
import org.junit.Test;

public class UserTest {

    @Test
    public void ok() {
        User u = new User();
        u.setId(1);
        u.setAge(10);
        u.setName("zxy");
        u.setPassword("123456");
        u.setCreateTime(new Date());

        Assert.assertEquals(10, u.getAge());
        Assert.assertEquals("zxy", u.getName());

        System.out.println(new User(1, "zxy", 12, "123456", new Date()));

        UserVO vo = UserMapper.INSTANCE.toUserVO(u);
        System.out.println("vo: "+vo);
        Assert.assertEquals("zxy", vo.getName());
        Assert.assertEquals("xx-1", vo.getCode());
        Assert.assertEquals("hello zxy", vo.getHello());
    }
}

简单看一下生成的代码

UserMapperImpl.java

 // Source code is unavailable, and was generated by the Fernflower decompiler.
package com.zxy.demo;

import java.text.SimpleDateFormat;

public class UserMapperImpl implements UserMapper {
   public UserVO toUserVO(User user) {
      if (user == null) {
         return null;
      } else {
         UserVO userVO = new UserVO();
         if (user.getCreateTime() != null) {
            userVO.setCreateTime((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).format(user.getCreateTime()));
         }

         userVO.setId(user.getId());
         userVO.setName(user.getName());
         userVO.setAge(user.getAge());
         userVO.setCode("xx-" + user.getId());
         userVO.setHello(UserMapper.hello(user));
         return userVO;
      }
   }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

鸿蒙开发(NEXT/API 12)【硬件(传感器开发)】传感器服务

使用场景 Sensor Service Kit&#xff08;传感器服务&#xff09;使应用程序能够从传感器获取原始数据&#xff0c;并提供振感控制能力。 Sensor&#xff08;传感器&#xff09;模块是应用访问底层硬件传感器的一种设备抽象概念。开发者可根据传感器提供的相关接口订阅传感器…

The 2024 CCPC Online Contest (C I J三题思路)

写在前面 因为学弟已经问了几个题了&#xff0c;于是乎这场没有vp&#xff0c;准备直接开写了 题目 C. 种树&#xff08;树形dp&#xff09; 题解 只有两种情况&#xff0c; 一种是1-2-3&#xff0c;1是2的父亲&#xff0c;2是3的父亲 另一种是1-2-3&#xff0c;2同时是1…

【网络安全】-访问控制-burp(1~6)

文章目录 前言   1.Lab: Unprotected admin functionality  2.Lab: Unprotected admin functionality with unpredictable URL   3.Lab: User role controlled by request parameter   4.Lab:User role can be modified in user profile  5.Lab: User ID controlled by…

校园二手交易平台的小程序+ssm(lw+演示+源码+运行)

摘 要 随着社会的发展&#xff0c;社会的方方面面都在利用信息化时代的优势。互联网的优势和普及使得各种系统的开发成为必需。 本文以实际运用为开发背景&#xff0c;运用软件工程原理和开发方法&#xff0c;它主要是采用java语言技术和mysql数据库来完成对系统的设计。整个…

【JavaEE】——线程池大总结

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯&#xff0c; 你们的点赞收藏是我前进最大的动力&#xff01;&#xff01;希望本文内容能够帮助到你&#xff01; 目录 引入&#xff1a;问题引入 一&#xff1a;解决方案 1&#xff1a;方案一——协程/纤程 &#xff08;1…

多输入多输出预测 | NGO-BP北方苍鹰算法优化BP神经网络多输入多输出预测(Matlab)

多输入多输出预测 | NGO-BP北方苍鹰算法优化BP神经网络多输入多输出预测&#xff08;Matlab&#xff09; 目录 多输入多输出预测 | NGO-BP北方苍鹰算法优化BP神经网络多输入多输出预测&#xff08;Matlab&#xff09;预测效果基本介绍程序设计往期精彩参考资料 预测效果 基本介…

计算机毕业设计 在线问诊系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

市场调研利器 网络问卷的优势及面临的挑战

网络问卷作为市场调研工具&#xff0c;高效便捷、成本低廉、数据准确度高且灵活多样。但其低响应率、数据偏差、隐私与安全及技术依赖等挑战也需关注。企业应优化调研方法&#xff0c;应对挑战&#xff0c;以获取全面市场信息。 一、网络问卷的优势 首先&#xff0c;我们来分析…

vue3 通过 axios + jsonp 实现根据公网 ip, 查询天气信息

前提 安装 axios 的 jsonp 适配器。 pnpm install pingtou/axios-jsonp 简单使用说明&#xff1a;当与后端约定的请求 callback 参数名称不为为 callback 时&#xff0c;可修改。一般无需添加。 1. 获取当前电脑 ip 和城市信息 请求地址&#xff1a; https://whois.pconl…

国庆假节高速免费通行全攻略

关注▲洋洋科创星球▲一起成长&#xff01; 国庆节假期全国收费公路继续对7座以下&#xff08;含7座&#xff09;小型客车免收车辆通行费。 具体免费时段从 10月1日00&#xff1a;00开始 10月7日24&#xff1a;00结束 01 提前出发&#xff0c;免费离开&#xff1a; 如果你在…

视频分割怎么弄?国内外Top 7视频剪辑软件大盘点,新媒体必看!

视频是一种记录美好回忆的工具&#xff0c;无论过去的经历是搞笑还是尴尬&#xff0c;我们总能与他人一同回味那些时光。如果您对某部电影中的特定片段情有独钟&#xff0c;您可以寻找视频分割工具&#xff0c;轻松地对视频进行剪切和合并。分割视频的过程就像剪纸&#xff0c;…

【Oauth2整合gateway网关实现微服务单点登录】

文章目录 一.什么是单点登录&#xff1f;二.Oauth2整合网关实现微服务单点登录三.时序图四.代码实现思路1.基于OAuth2独立一个认证中心服务出来2.网关微服务3产品微服务4.订单微服务5.开始测试单点登录 一.什么是单点登录&#xff1f; 单点登录&#xff08;Single Sign On&…

代码随想录算法训练营第十七天|654.最大二叉树 617.合并二叉树 700.二叉搜索树中的搜索 98.验证二叉搜索树

654.最大二叉树 给定一个不含重复元素的整数数组。一个以此数组构建的最大二叉树定义如下&#xff1a; 二叉树的根是数组中的最大元素。左子树是通过数组中最大值左边部分构造出的最大二叉树。右子树是通过数组中最大值右边部分构造出的最大二叉树。 通过给定的数组构建最大二…

江科大笔记——新建工程

STM32的开发方式 目前STM32的开发方式主要有基于寄存器的方式、基于标准库的方式&#xff08;库函数的方式&#xff09;、基于HAL库的方式&#xff1a; 基于库函数的方式是使用ST官方提供的封装好的函数&#xff0c;通过调用这些函数来间接地配置寄存器。基于HAL库的方式可以…

【Linux】初始进程

目录 基本概念 PCB task_struct task_struct内容分类 组织进程 查看进程 查看正在运行的进程信息 获取pid和ppid 创建子进程 基本概念 一个已经加载到内存中的程序&#xff0c;叫做进程&#xff0c;正在运行的程序&#xff0c;叫做进程&#xff0c;进程是担当分配系统…

解决QT开发由于中文导致的编译错误以及输出内容乱码问题

在进行QT程序开发时&#xff0c;大家可能或者一定会遇到的问题就是中文乱码问题&#xff0c;这个乱码问题可能是在你看代码的显示上&#xff0c;也可能在程序的输出上&#xff0c;甚至还有可能导致你的代码直接编译失败&#xff0c;都有可能和中文编码有关&#xff0c;还有一些…

【Day20240924】联邦学习中的方法 改进

文章目录 前言一、FedAvg二、FedProx三、MOON四、FedDyn五、FedAsync六、PORT七、ASO-Fed八、FedBuff九、FedSA 前言 几种异步的方法&#xff1a; FedAsync PORT ASO-Fed FedBuff FedSA 几种同步的方法&#xff1a; FedAvg FedProx MOON FedDyn 一、FedAvg FedAvg基本步骤&a…

[SAP ABAP] 锁对象

在SAP中使用锁对象&#xff0c;用于避免在数据库中插入或更改数据时出现不一致的情况 1.创建锁对象 数据准备 学校表(ZDBT_SCH_437) 使用事务码SE11创建锁对象 点击"锁对象"单选按钮&#xff0c;输入以E开头的锁定对象的名称&#xff0c;然后点击创建按钮 锁对象名…

QT基础 制作简单登录界面

作业&#xff1a; 1、创建一个新项目&#xff0c;将默认提供的程序都注释上意义 01zy.pro代码 QT core gui # QT表示要引入的类库 core&#xff1a;核心库例如IO操作在该库中 gui&#xff1a;图形化界面库 # 如果要使用其他类库中的相关函数&#xff0c;则需要加对…

如何使用ssm实现基于web的学生就业管理系统的设计与实现+vue

TOC ssm726基于web的学生就业管理系统的设计与实现vue 第1章 绪论 1.1 课题背景 二十一世纪互联网的出现&#xff0c;改变了几千年以来人们的生活&#xff0c;不仅仅是生活物资的丰富&#xff0c;还有精神层次的丰富。在互联网诞生之前&#xff0c;地域位置往往是人们思想上…