当前位置: 首页 > article >正文

SpringBoot 对象转换 MapStruct

文章目录

    • 工作原理
    • 核心优势
    • 为什么不使用 `BeanUtils`
    • 使用步骤
      • 添加依赖
      • 定义实体类和VO类
      • 定义映射接口
      • 测试数据
    • 参考

工作原理

基于 Java 的 JSR 269 规范,该规范允许在编译期处理注解,也就是 Java 注解处理器。MapStruct 通过定义的注解处理器,在编译期读取映射接口,并生成相应的实现类。这个过程中,它会解析接口中声明的映射方法,并创建对应的 getters 和 setters 调用

核心优势

  1. 零反射:生成的代码直接调用对象的 getter/setter,性能接近手写代码。
  2. 编译时检查:映射错误(如字段不匹配)在编译时暴露,而非运行时。
  3. 灵活性:支持自定义转换逻辑、嵌套对象映射、集合转换等。
  4. 与 IDE 兼容:生成的代码可调试,便于跟踪问题。

为什么不使用 BeanUtils

在高并发的场景中,性能是最为重要的,BeanUtils 虽然可以快速完成 JavaBean 之间的转换,但是底层逻辑是基于反射实现的,这样会导致在高并发场景中性能下降,这时候最高效的处理办法就是手动的 getter/setter,但是要大量处理这些可重复的操作会浪费大量时间,因此可以使用 MapStruct 解决

区别:

  • 编译时生成代码 vs 运行时反射MapStruct生成的映射代码是在编译时生成的,而BeanUtils则是在运行时使用反射机制实现转换
  • 性能和可扩展性:由于 MapStruct 生成的代码是类型安全的,因此可以比使用反射更加高效和可靠。同时,MapStruct 还能够自定义转换逻辑并支持扩展,使得它更加灵活和可扩展。
  • 集成方式:MapStruct 可以无缝集成到 Spring 中,也可以与其他 IoC 容器结合使用;而 BeanUtils 是 Spring 框架自带的工具类。
  • 映射规则的定义方式:MapStruct 使用基于注解的方式在源代码中定义映射规则,而 BeanUtils 则需要手动编写复杂的转换方法。

使用步骤

添加依赖

pom.xml 中添加依赖

<dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct</artifactId><version>1.5.5.Final</version>
</dependency>
<!-- 注解处理器 -->
<dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>1.5.5.Final</version><scope>provided</scope>
</dependency>
  • mapstruct 依赖
    • 提供了 MapStruct 的核心注解,如 @Mapper@Mapping,用于定义映射接口
    • 在运行时,如果使用默认的组件模型(default),还需要依赖 Mappers.getMapper(…) 方法来获取映射器实例
  • mapstruct-processor 依赖
    • 注解处理器(annotation processor),在编译阶段扫描带有 MapStruct 注解的接口,并生成对应的实现类
    • 不会在运行时参与应用程序的执行,因此通常不需要在运行时包含此依赖

根据 mapstruct-processor 依赖的定义,其实不应该将该依赖放在 dependencies 标签中,而是将 mapstruct-processor 作为注解处理器添加到 maven-compiler-plugin 的 annotationProcessorPaths 中,这也是官方推荐处理

...
<properties><org.mapstruct.version>1.6.3</org.mapstruct.version>
</properties>
...
<dependencies><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct</artifactId><version>${org.mapstruct.version}</version></dependency>
</dependencies>
...
<build><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>${org.mapstruct.version}</version></path></annotationProcessorPaths></configuration></plugin></plugins>
</build>
...

如果是在已经有 Lombok 依赖的项目中加入 mapstruct 依赖需要注意这两个注解处理器的配置,不然在 mvn install 等命令执行时可能会发生关于 Lombok 依赖相关的错误

Lombok 1.18.16 引入了一个破坏性更改(变更日志)。必须添加额外的注解处理器 lombok-mapstruct-binding (Maven),否则 MapStruct 将无法与 Lombok 兼容。

<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.13.0</version><configuration><source>17</source><target>17</target><annotationProcessorPaths><path><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>${mapstruct.version}</version></path><path><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${lombok.version}</version></path><!-- Lombok 版本从 1.18.16 开始必须添加,其他版本为可选 --><!-- Lombok 与 MapStruct 的绑定处理器 --><path><groupId>org.projectlombok</groupId><artifactId>lombok-mapstruct-binding</artifactId><version>0.2.0</version></path></annotationProcessorPaths></configuration></plugin></plugins>
</build>

定义实体类和VO类

// 实体类
@Data  
@Accessors(chain = true)  
public class Student implements Serializable {  private Long  id;  private String name;  private Integer age;  private String studentNo;  }// VO类
@Data  
@Accessors(chain = true)  
public class StudentVO implements Serializable {  private Long voId;  private String voName;  private Integer voAge;  private String voStudentNo;  }

定义映射接口

定义抽象接口

@MapperConfig  
public interface IMapping<SOURCE, TARGET> {  TARGET sourceToTarget(SOURCE source);  // 反向映射(需配置反向方法)@InheritInverseConfiguration(name = "sourceToTarget")  SOURCE targetToSource(TARGET target);  @InheritConfiguration(name = "sourceToTarget")  List<TARGET> sourceToTarget(List<SOURCE> sourceList);  @InheritConfiguration(name = "sourceToTarget")  List<SOURCE> targetToSource(List<TARGET> targetList);  List<TARGET> sourceToTarget(Stream<SOURCE> stream);  List<SOURCE> targetToSource(Stream<TARGET> stream);  }

定义映射接口,@Mapper(componentModel = "spring")。默认情况下,mapstruct 生成的 Mapper 实现类不会被 Spring 容器管理。如果不指定 componentModel,需要通过 StudentMapping mapper = Mappers.getMapper(StudentMapping.class) 手动获取 Mapper 实例。

通过设置 componentModel = "spring"mapstruct 会在生成的实现类上添加 @Component 注解,使其成为 Spring 管理的 Bean,从而可以在其他组件中通过依赖注入方式使用。因此,StudentMapping 接口通过 @Mapper(componentModel = "spring") 注解,其实现类被注册为 SpringBean,可以通过构造函数注入方式使用该 Mapper

@Mapper(componentModel = "spring")  
public interface StudentMapping extends IMapping<Student, StudentVO>{  @Override  @Mapping(source = "id", target = "voId")  @Mapping(source = "name", target = "voName")  @Mapping(source = "age", target = "voAge")  @Mapping(source = "studentNo", target = "voStudentNo")  StudentVO sourceToTarget(Student student);  @Override  List<StudentVO> sourceToTarget(List<Student> students);  
}

测试数据

默认情况下,Spring 在实例化测试类时使用无参构造函数,并通过字段注入(@Autowired)方式注入依赖。而使用 @TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL) 注解后,Spring 会尝试使用带参数的构造函数,并通过构造函数注入方式自动注入所需的依赖,也就是可以使用 Lombok 依赖中的 @RequiredArgsConstructor 注解

@SpringBootTest  
@RequiredArgsConstructor  
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)  
public class StudentMapStructTest {  private final StudentMapping studentMapping;  @Test  public void test() {  Student student = new Student();  student.setId(1L);  student.setName("Mayer");  student.setAge(18);  student.setStudentNo("20250001");  StudentVO studentVO = studentMapping.sourceToTarget(student);  System.out.println(studentVO);  System.out.println(studentMapping.targetToSource(studentVO));  }  }

打印结果

StudentVO(voId=1, voName=Mayer, voAge=18, voStudentNo=20250001)
Student(id=1, name=Mayer, age=18, studentNo=20250001)

参考

  • Java-Mapstruct 实践 | 无垠之境
  • Java Review - MapStruct 全掌握:8 个案例探究高效快捷的 Java 对象映射 - CharyGao - 博客园
  • 【MapStruct】还在用BeanUtils?不如试试MapStruct - CharyGao - 博客园

相关文章:

SpringBoot 对象转换 MapStruct

文章目录 工作原理核心优势为什么不使用 BeanUtils使用步骤添加依赖定义实体类和VO类定义映射接口测试数据 参考 工作原理 基于 Java 的 JSR 269 规范&#xff0c;该规范允许在编译期处理注解&#xff0c;也就是 Java 注解处理器。MapStruct 通过定义的注解处理器&#xff0c;…...

计算机网络——Session、Cookie 和 Token

在 Web 开发中&#xff0c;Session、Cookie 和 Token 是实现用户会话管理和身份验证的核心技术。它们既有联系&#xff0c;也有明显区别。以下从定义、原理、联系、区别和应用场景等方面详细解析。 一、基本定义与原理 1. Cookie 定义&#xff1a; 是浏览器存储在客户端的小…...

01-jenkins学习之旅-window-下载-安装-安装后设置向导

1 jenkins简介 百度百科介绍&#xff1a;Jenkins是一个开源软件项目&#xff0c;是基于Java开发的一种持续集成工具&#xff0c;用于监控持续重复的工作&#xff0c;旨在提供一个开放易用的软件平台&#xff0c;使软件项目可以进行持续集成。 [1] Jenkins官网地址 翻译&…...

Spark,SparkSQL操作Mysql, 创建数据库和表

以下是使用 Spark SQL 在 MySQL 中创建数据库和表的步骤&#xff08;基于 Scala API&#xff09;&#xff1a; 1. 准备工作 - 添加 MySQL 驱动依赖 同前所述&#xff0c;需在 Spark 环境中引入 MySQL Connector JAR 包&#xff08;如 mysql-connector-java-8.0.33.jar &#…...

AttributeError: module ‘cv2.dnn‘ has no attribute ‘DictValue‘错误解决方法

源代码如下&#xff1a; # 读取图像 import cv2 im cv2.imread("./test.png", 1) # 1表示3通道彩色&#xff0c;0表示单通道灰度 cv2.imshow("test", im) # 在test窗口中显示图像 print(type(im)) # 打印数据类型 print(im.shape) # 打印图像尺寸 cv2.wai…...

HarmonyOS 鸿蒙应用开发基础:@Watch装饰器详解及与@Monitor装饰器对比分析

在鸿蒙系统的开发中&#xff0c;状态管理和组件之间的通信是至关重要的部分。为此&#xff0c;鸿蒙提供了多种装饰器来帮助开发者监听和处理数据变化。今天我们将深入探讨Watch装饰器&#xff0c;并与新的状态管理组件V2中的Monitor装饰器进行对比。 Watch装饰器详解 基本概念…...

机器人拖动示教控制

机器人拖动示教控制 机器人拖动视角控制与轨迹记录 1. 知识目标 体验ES机器人拖动视角操作体验ES机器人拖动轨迹记录 2. 技能目标 掌握ES机器人拖动视角操作掌握ES机器人拖动轨迹记录 3. ES机器人拖动视角操作 3.1 操作步骤 点击“拖动视角”按钮长按“启用”键约3秒进入…...

免费开放试乘体验!苏州金龙自动驾驶巴士即将上线阳澄数谷

近日&#xff0c;苏州自动驾驶巴士线路——阳澄数谷示范线正式上线&#xff0c;即日起向全民免费开放试乘体验&#xff01; 在苏州工业园区地铁3号线倪浜•阳澄数谷站外&#xff0c;一辆辆黑、白配色的小巴正在道路上有条不紊地行驶。与普通公交不同的是&#xff0c;小巴造型奇…...

matlab加权核范数最小化图像去噪

加权核范数最小化&#xff08;Weighted Nuclear Norm Minimization, WNNM&#xff09;是一种有效的图像去噪方法&#xff0c;它通过最小化加权核范数来促进图像的低秩近似&#xff0c;同时保留图像的边缘和细节信息。这种方法在去除噪声的同时&#xff0c;能够较好地保留图像的…...

docker容器暴露端口的作用

Docker 镜像中**暴露的端口&#xff08;通过 EXPOSE 指令声明&#xff09;**主要有以下作用和意义&#xff1a; 1. 文档化作用&#xff08;Documentation&#xff09; 显式声明容器内部服务监听的端口&#xff0c;告知用户或开发者该镜像提供的服务需要通过哪些端口通信。例如…...

每日Prompt:像素风格插画

提示词 像素风格插画&#xff0c;日式漫画脸&#xff0c;画面主体为一位站在路边的男孩&#xff0c;人物穿着黑色冲锋衣&#xff0c;手里拿着手机&#xff0c;男孩靠坐在机车旁边&#xff0c;脚边依偎着一只带着小摩托车头盔的小小猫&#xff0c;背景是雨中&#xff0c;身旁停…...

Windows逆向工程提升之二进制分析工具:HEX查看与对比技术

公开视频 -> 链接点击跳转公开课程博客首页 -> ​​​链接点击跳转博客主页 目录 十六进制查看工具 应用于逆向工程的知识点 ​编辑 二进制对比工具 应用于逆向工程的知识点 十六进制查看工具 十六进制查看器是逆向工程的基础工具&#xff0c;它可以以十六进制格式…...

Android10如何设置ro.debuggable=1?

说明&#xff1a;仅供学习使用&#xff0c;请勿用于非法用途&#xff0c;若有侵权&#xff0c;请联系博主删除 作者&#xff1a;zhu6201976 目录 一、背景 二、如何解决&#xff1f; 三、操作步骤 一、背景 Android 10 开始的限制&#xff1a;ro.debuggable 是只读属性 从 …...

2024游戏安全白皮书:对抗激烈!PC游戏外挂功能数增长超149%,超85%移动外挂为定制挂(附获取方式)

2024 年&#xff0c;中国游戏市场实际销售收入达 3257.83 亿元&#xff0c;同比增长 7.53%&#xff1b;用户规模 6.74 亿人&#xff0c;同比增长 0.94%&#xff0c;再创新高。这份庞大的数据背后&#xff0c;更是对安全防线实力的严峻拷问。 在广东省游戏产业协会的指导下&…...

深度解析:Spark、Hive 与 Presto 的融合应用之道

目录 一、Spark分布式部署基础 1.1 Spark部署模式概述 1.2 Standalone模式部署 1.3 YARN模式部署 1.4 Kubernetes模式部署 1.5 Spark关键配置参数优化 1.6 Spark高可用配置 二、Apache Thrift 在大数据生态中的核心作用 2.1 基础概念 2.2 在大数据中的应用 2.3 Beel…...

12kV 环保气体绝缘交流金属封闭开关设备现场交流耐压试验规范

范围 本文件规定了12kV环保气体绝缘交流金属封闭开关设备现场交流耐压试验的被试设备及试验接线、试验条件、试验步骤、试验判据及异常处理方法。 本文件适用于12kV环保气体绝缘交流金属封闭开关设备现场交流耐压试验&#xff0c;其他气体绝缘交流金属封闭开关设备可参照执行。…...

位图算法——判断唯一字符

这道题有多种解法&#xff0c;可以创建hash数组建立映射关系判断&#xff0c;但不用新的数据结构会加分&#xff0c;因此我们有“加分”办法——用位图。 我们可以创建一个整型变量&#xff08;32位&#xff09;而一共才26个字母&#xff0c;所以我们只要用到0-25位即可&#…...

HarmonyOS 鸿蒙应用开发基础:父组件调用子组件方法的几种实现方案对比

在ArkUI声明式UI框架中&#xff0c;父组件无法直接调用子组件的方法。本文介绍几种优雅的解决方案&#xff0c;并作出对比分析&#xff0c;分析其适用于不同场景和版本需求。帮助开发者在开发中合理的选择和使用。 方案一&#xff1a;Watch装饰器&#xff08;V1版本适用&#x…...

复盘20250522

根据行业前景、公司基本面、技术壁垒及市场催化因素的综合分析&#xff0c;以下两支个股最有可能持续上涨&#xff1a; 1. 汇得科技&#xff08;机器人化工&#xff09; 核心逻辑&#xff1a; 高增长赛道验证&#xff1a;公司动力电池配套产品&#xff08;水冷板缓冲垫、保温…...

【UE5】环形菜单教程

效果 步骤 1. 下载图片资源&#xff1a;百度网盘 请输入提取码 提取码:fjjx 2. 将图片资源导入工程&#xff0c;如下 3. 新建3个控件蓝图&#xff0c;这里分别命名为“WBP_CircularMenu”、“WBP_Highlight”、“WBP_Icon” 4. 打开“WBP_Icon”&#xff0c;设置“所需” 添加…...

Athena 执行引擎:在线服务计算的效率王者

引言 在在线服务领域&#xff0c;计算任务呈现出独特的特性&#xff1a;一方面&#xff0c;数据量通常不会过于庞大&#xff0c;因为在线服务对耗时和响应速度有着严苛要求&#xff1b;另一方面&#xff0c;计算任务具有可控性&#xff0c;其大多并非由用户实时输入动态生成&a…...

飞桨paddle ‘ParallelEnv‘ object has no attribute ‘_device_id‘【已解决】

书借上回&#xff0c;自从我反复重装paddle之后&#xff0c;我发现了&#xff0c;只要pip list中有库&#xff0c;但是代码报错&#xff0c;那就是飞桨没把代码更新完全&#xff0c;只能自己去改源代码 我又遇到报错了&#xff1a; 根据报错信息&#xff0c;找到ParallelEnv报…...

Bert预训练任务-MLM/NSP

MLM MLM:Masked Language Mode:在每一个训练序列中以15%的概率随机地选中某个token进行MASK,当一个token被选中后&#xff0c;有以下三种处理方式&#xff1a; 80%的概率被[MASK]&#xff0c;如my dog is hairy->my dog is [MASK]10%的概率修改为随机的其他token,如my dog …...

微信小程序之Promise-Promise初始用

我们来尝试使用Promise。 1、需求&#xff0c;做个抽奖的按钮&#xff0c; 抽奖规则&#xff1a; 30%的几率中奖&#xff0c;中奖会提示恭喜恭喜&#xff0c;奖品为10万 RMB 劳斯莱斯优惠券&#xff0c;没中奖会提示再接再厉。 2、先搭界面&#xff1a; <view class&qu…...

准备好,开始构建:由 Elasticsearch 向量数据库驱动的 Red Hat OpenShift AI 应用程序

作者&#xff1a;来自 Elastic Tom Potoma Elasticsearch 向量数据库现在被 “基于 LLM 和 RAG 的 AI 生成” 验证模式支持。本文将指导你如何开始使用。 Elasticsearch 已原生集成业内领先的生成式 AI 工具和服务提供商。欢迎观看我们的网络研讨会&#xff0c;了解如何突破 RA…...

spring的注入方式都有什么区别

目录 1. 构造器注入&#xff08;Constructor Injection&#xff09; 2. Setter 注入&#xff08;Setter Injection&#xff09; 3. 字段注入&#xff08;Field Injection&#xff09; 4. 接口注入&#xff08;Interface Injection&#xff09; 主要区别对比 最佳实践 总…...

RNN神经网络

RNN神经网络 1-核心知识 1-解释RNN神经网络2-RNN和传统的神经网络有什么区别&#xff1f;3-RNN和LSTM有什么区别&#xff1f;4-transformer的归一化有哪几种实现方式 2-知识问答 1-解释RNN神经网络 Why&#xff1a;与我何干&#xff1f; 在我们的生活中&#xff0c;很多事情…...

Linux | 开机自启动设置多场景实现

注&#xff1a;本文为“Llinux 设置开机自启”相关文章合辑。 略作重排&#xff0c;未整理去重。 如有内容异常&#xff0c;请看原文。 Linux 设置开机自启动的三种方法 幽夜卡尔 2022-10-22 一、在 /etc/rc.local 文件中添加自启动命令 编辑文件&#xff1a;执行以下命令&a…...

杨校老师竞赛课之青科赛GOC3-4年级组模拟题

1.山峰&#xff08;程序填空&#xff09;程序填空题 题目描述 编程画出山峰。 要求&#xff1a;两个正三角形&#xff0c;三角形边长分别是200、100&#xff0c;山峰的颜色为8号色。 int main() {// 绘制等边三角形&#xff08;边长100&#xff09;&#xff1a;右转30度调整…...

设计杂谈-工厂模式

“工厂”模式在各种框架中非常常见&#xff0c;包括 MyBatis&#xff0c;它是一种创建对象的设计模式。使用工厂模式有很多好处&#xff0c;尤其是在复杂的框架中&#xff0c;它可以带来更好的灵活性、可维护性和可配置性。 让我们以 MyBatis 为例&#xff0c;来理解工厂模式及…...