【SpringBoot】26 实体映射工具(MapStruct)
Gitee 仓库
https://gitee.com/Lin_DH/system
介绍
现状
为了让应用程序的代码更易于维护,通常会将项目进行分层。在《阿里巴巴 Java 开发手册》中,推荐分层如下图所示:
每层都有对应的领域模型,即不同类型的 Bean。
- DO(Data Object):与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。
- DTO(Data Transfer Object):数据传输对象,Service 或 Manager 向外传输的对象。
- BO(Business Object):业务对象,由 Service 层输出的封装业务逻辑的对象。
- AO(Application Object):应用对象,在 Web 层与 Service 层之间抽象的复用对象模型,极为贴近展示层,复用度不高。
- VO(View Object):显示层对象,通常是 Web 向模板渲染引擎层传输的对象。
- Query:数据查询对象,各层接收上层的查询请求。超过两个参数的查询封装,禁止使用 Map 类进行传输。
痛点
由于代码分层的原因,就会导致代码中有多种 Bean,如 UserVO,UserDTO,UserDO 等,并且经常发生各种 VO / DTO / DO 之间的转换。从而产生很多 vo.setUsername(dto.getUsername()) 的代码。当字段多了不仅容易出错,而且很浪费开发时间。也有使用 BeanUtils.copyProperties() 进行转换,这样虽然减少了开发时间和代码,但依然存在问题。如:1)利用反射导致性能不好;2)不同名称的属性无法直接进行映射。
解决方案
本次使用的 Java 实体对象映射框架是 MapStruct 。MapStruct基于 JSR 269 的 Java 注解处理器,用于生成类型安全,高性能,无依赖的 Bean 映射代码,自动生成对象的代码,使用便捷,性能优越。
特点
- 1)通过 getter / setter 进行字段拷贝,而不是利用反射机制。
- 2)字段名称相同直接转换,名称不同使用 @Mapping 注解标识。
区别
与动态映射框架相比,MapStruct 的优势:
- 1)使用普通的 getter / setter 方法,而不是反射机制,执行更快,性能更好。
- 2)编译时类型安全。
- 3)清晰的错误提示信息。
依赖
pom.xml
需要引入 mapstruct 和 mapstruct-processor,同时 scope 设置为 provided ,即它只影响到编译,测试阶段。
<dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct</artifactId><version>1.5.0.Final</version><scope>provided</scope>
</dependency><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>1.5.0.Final</version><scope>provided</scope>
</dependency>
代码实现
第一步:编写 Student 实体类
Student.java
package com.lm.system.common;import lombok.*;import java.io.Serializable;
import java.util.Date;/*** @author DUHAOLIN* @date 2024/11/12*/
@Data
@Builder
public class Student implements Serializable {private static final long serialVersionUID = 1L;private Integer id;private String name;private Integer age;private String gender;private Date createTime;}
第二步:编写 StudentVO 实体类
StudentVO.java
package com.lm.system.common.dto;import lombok.Data;/*** @author DUHAOLIN* @date 2024/11/12*/
@Data
public class StudentVO {private Integer userId;private String username;private Integer age;private String gender;}
第三步:编写实体类转换接口
StudentConvert.java
package com.lm.system.convert;import com.lm.system.common.Student;
import com.lm.system.common.dto.StudentVO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;/*** @author DUHAOLIN* @date 2024/11/12*/
@Mapper
public interface StudentConvert {/*** 获取该类自动生成的实体类实例*/StudentConvert INSTANCES = Mappers.getMapper(StudentConvert.class);@Mappings({@Mapping(source = "id", target = "userId"),@Mapping(source = "name", target = "username")})StudentVO toStudentVO(Student student);}
第四步:编写测试类
MapStructTest.java
package com.lm.system.test;import com.lm.system.common.Student;
import com.lm.system.common.dto.StudentVO;
import com.lm.system.convert.StudentConvert;
import org.junit.Test;import java.util.Date;/*** @author DUHAOLIN* @date 2024/11/12*/
public class MapStructTest {@Testpublic void testStudent() {Student student = getStudent();System.out.println(student);StudentVO studentVO = StudentConvert.INSTANCES.toStudentVO(student);System.out.println(studentVO);}private Student getStudent() {return Student.builder().id(1).name("Tom").age(18).gender("男").createTime(new Date()).build();}}
效果图
属性处理
简单属性
当 gender 传入的是男或女,需要转换成对应的0或1,再传入数据库时,则需要进行处理。
StudentConvert.java
@Mappings({@Mapping(source = "id", target = "userId"),@Mapping(source = "name", target = "username"),@Mapping(target = "gender", expression = "java(student.getGender() == \"男\" ? \"0\" : \"1\")")
})
StudentVO toStudentVO(Student student);
复杂属性
限制输入年龄的数值。
StudentConvert.java
package com.lm.system.convert;import com.lm.system.common.Student;
import com.lm.system.common.dto.StudentVO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.Named;
import org.mapstruct.factory.Mappers;/*** @author DUHAOLIN* @date 2024/11/12*/
@Mapper
public interface StudentConvert {/*** 获取该类自动生成的实体类实例*/StudentConvert INSTANCES = Mappers.getMapper(StudentConvert.class);@Mappings({@Mapping(source = "id", target = "userId"),@Mapping(source = "name", target = "username"),@Mapping(target = "gender", expression = "java(student.getGender() == \"男\" ? \"0\" : \"1\")"),@Mapping(source = "age", target = "age", qualifiedByName = "transferAge")})StudentVO toStudentVO(Student student);@Named("transferAge")default Integer transferAge(Integer age) {if (age < 0) {return 0;}else if (age > 120){return 120;}else {return age;}}}
Spring中使用
如果在 Spring 中使用,需要修改组件模型为 spring,可以通过 pom.xml 参数修改,也可以通过注解修改。修改后会在实现类上添加 @Component 注解,从而成为一个 Bean,加入 Spring 容器中。
StudentConvert.java
@Mapper(componentModel = "spring")
public interface StudentConvert {}
报错
如果遇到报错:java.lang.NoSuchMethodError,则在 IDEA 右侧的 Maven 选项中,运行 clean 和 compile,再进行重试。
项目结构图
参考链接
推荐一款Java实体映射工具—mapstruct:【https://www.cnblogs.com/lvmengtian/p/14594185.html】
【springboot进阶】优雅使用 MapStruct 进行类复制:【https://blog.csdn.net/lrb0677/article/details/127838138】
芋道 Spring Boot 对象转换 MapStruct 入门:【https://www.iocoder.cn/Spring-Boot/MapStruct/?self】
相关文章:

【SpringBoot】26 实体映射工具(MapStruct)
Gitee 仓库 https://gitee.com/Lin_DH/system 介绍 现状 为了让应用程序的代码更易于维护,通常会将项目进行分层。在《阿里巴巴 Java 开发手册》中,推荐分层如下图所示: 每层都有对应的领域模型,即不同类型的 Bean。 DO&…...

分层架构 IM 系统之架构演进
在电商业务日活几百万的情况下,IM 系统采用分层架构方式,如下图。 分层架构的 IM 系统,整体上包含了【终端层】、【入口层】、【业务逻辑层】、【路由层】、【数据访问层】和【存储层】,我们在上篇文章(分层架构 IM 系…...

基于YOLOv8深度学习的医学影像阿尔兹海默症检测诊断系统研究与实现(PyQt5界面+数据集+训练代码)
阿尔茨海默症(Alzheimer’s disease)是一种常见的神经退行性疾病,主要表现为记忆丧失、认知能力下降以及行为和人格改变。随着全球老龄化问题的加剧,阿尔茨海默症的发病率也在逐年上升,给患者及其家庭带来了巨大的经济…...

【支持向量机(SVM)】:相关概念及API使用
文章目录 1 SVM相关概念1.1 SVM引入1.1.1 SVM思想1.1.2 SVM分类1.1.3 线性可分、线性和非线性的区分 1.2 SVM概念1.3 支持向量概念1.4 软间隔和硬间隔1.5 惩罚系数C1.6 核函数 2 SVM API使用2.1 LinearSVC API 说明2.2 鸢尾花数据集案例2.3 惩罚参数C的影响 1 SVM相关概念 1.1…...

Android kotlin之配置kapt编译器插件
配置项目目录下的gradle/libs.versions.toml文件,添加kapt配置项: 在模块目录下build.gradle.kt中增加 plugins {alias(libs.plugins.android.application)alias(libs.plugins.jetbrains.kotlin.android)// 增加该行alias(libs.plugins.jetbrains.kotl…...
时序数据库TDEngine
TDengine 是一款开源、高性能、云原生的时序数据库(Time Series Database, TSDB), 它专为物联网、车联网、工业互联网、金融、IT 运维等场景优化设计。同时它还带有内建的缓存、流式计算、数据订阅等系统功能,能大幅减少系统设计的复杂度&…...
jd-easyflow中inclusive的用法
在jd-easyflow中,inclusive通常与流程中的条件分支(conditions)配置相关,用于控制多个条件分支的执行逻辑。当conditionType设置为inclusive时,表示多个条件分支中的所有条件都会被评估,而不是像exclusive那…...

sqlmap图形化安装使用(附文件)
1.需要python环境,我这里就不教如何安装python环境了。 2.下载压缩包并且解压 3. 凭自己喜好选择大窗口小窗口 4.进入图形化界面后,1.输入url地址。2.选择要执行的操作。3.构造命令语句 5.点击一把梭,然后就可以发现出结果了 6. 对于喜欢自己…...

从二维到一维:动态规划矩阵问题的优化之道
动态规划中的矩阵问题是非常经典的应用场景,比如最小路径和问题。这类问题很自然地可以想到使用二维 dp 数组来求解。 我们定义: dp[i][j] 表示从矩阵的第 i行第 j列到右下角的最小路径和。 基本解法 求解过程从右下角开始,向左上角遍历&am…...
计算机视觉(CV):让机器看懂世界
引言 计算机视觉(Computer Vision, CV)是人工智能的重要领域,致力于让机器能够“看懂”世界。CV技术广泛应用于自动驾驶、医疗影像、安防监控和娱乐领域,正在改变我们的生活方式。 本文将从基本概念、技术方法、应用场景和发展方向…...

记录下,用油猴Tampermonkey监听所有请求,绕过seesion
油猴Tampermonkey监听所有请求,绕过seesion 前因后果脚本编写 前因后果 原因是要白嫖一个网站的接口,这个接口的页面入口被隐藏掉了,不能通过页面调用,幸好之前有想过逆向破解通过账号密码模拟登录后拿到token,请求该…...
服务器产品
一 存储产品 3.1 3PAR 3.2 X10000 3.3 SAN Switch 3.4 Nimble 3.5 SimpliVity 3.6 XP 3.7 MSA 3.8 StoreOnce 3.9 StoreEver 3.10 StoreBlade 3.11 StoreEasy(WindowsNAS) 3.12 JBOD 3.13 CB 二 服务器产品 4.1 红牌服务器 4.1.1 红牌…...
pyhton django web集群基于linux定时任务
基于django management/commands目录下的脚本 from django.core.management import BaseCommand import logging import uuid from pia.utils.cache import reset_redis_expire from pia.utils.reids_key import TASK_KEYlogging logging.getLogger(task)""" …...
探索 Python 字典的奥秘:Future 对象为何能成为字典的键?
本质在于作为字典的key能不能执行hash(key) 问题 import concurrent.futuresdef task(n):return n * n# 创建一个线程池 with concurrent.futures.ThreadPoolExecutor() as executor:# 提交任务并获取 Future 对象future_to_num {executor.submit(task, i): i for i in rang…...

多品牌摄像机视频平台EasyCVR视频融合平台+应急布控球:打造城市安全监控新体系
在当今快速发展的智慧城市和数字化转型浪潮中,视频监控技术已成为提升公共安全、优化城市管理、增强应急响应能力的重要工具。EasyCVR视频监控平台以其强大的多协议接入能力和多样化的视频流格式分发功能,为用户提供了一个全面、灵活、高效的视频监控解决…...
Spark 中 RDD checkpoint 是通过启动两个独立的 Job 完成的。
在 Spark 中,RDD checkpoint 是通过启动两个独立的 Job 完成的。这两个 Job 分别用于生成 checkpoint 数据和更新依赖关系。下面从源码角度深入分析这个机制。 1. 为什么需要两个 Job? 当调用 RDD.checkpoint() 后: 第一个 Job:…...
如何下载TikTok视频没有水印
随着短视频平台的普及,TikTok(抖音国际版)成为了全球最受欢迎的社交媒体平台之一。它吸引了无数创作者发布自己的短视频内容,内容涵盖了舞蹈、搞笑、挑战、教程、旅行等各个方面。与此用户也常常希望能够下载自己喜欢的TikTok视频…...
天童美语:提升孩子的自信心的方法
每个孩子都渴望展翅高飞,但在成长的旅途中,难免会遇到风雨。不自信,就像一层薄雾,有时悄悄笼罩在孩子心头,阻碍了他们向阳而生的脚步。宁波天童教育认为,身为家长,我们的使命不仅是孩子的庇护伞…...

【网络编程】字节序:大端序和小端序
端序(Endianness),又称字节顺序,又称尾序,在计算机科学领域中,指存储器中或在数字通信链路中,组成多字节的字的字节的排列顺序。 在几乎所有的机器上,多字节对象都被存储为连续的字…...

视频融合×室内定位×数字孪生
随着物联网技术的迅猛发展,室内定位与视频融合技术在各行各业中得到了广泛应用。不仅能够提供精确的位置信息,还能通过实时视频监控实现全方位数据的可视化。 与此同时,数字孪生等技术的兴起为智慧城市、智慧工厂等应用提供了强大支持&#…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造,完美适配AGV和无人叉车。同时,集成以太网与语音合成技术,为各类高级系统(如MES、调度系统、库位管理、立库等)提供高效便捷的语音交互体验。 L…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...

高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...
unix/linux,sudo,其发展历程详细时间线、由来、历史背景
sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...

dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...

力扣热题100 k个一组反转链表题解
题目: 代码: func reverseKGroup(head *ListNode, k int) *ListNode {cur : headfor i : 0; i < k; i {if cur nil {return head}cur cur.Next}newHead : reverse(head, cur)head.Next reverseKGroup(cur, k)return newHead }func reverse(start, end *ListNode) *ListN…...
Spring AI Chat Memory 实战指南:Local 与 JDBC 存储集成
一个面向 Java 开发者的 Sring-Ai 示例工程项目,该项目是一个 Spring AI 快速入门的样例工程项目,旨在通过一些小的案例展示 Spring AI 框架的核心功能和使用方法。 项目采用模块化设计,每个模块都专注于特定的功能领域,便于学习和…...

归并排序:分治思想的高效排序
目录 基本原理 流程图解 实现方法 递归实现 非递归实现 演示过程 时间复杂度 基本原理 归并排序(Merge Sort)是一种基于分治思想的排序算法,由约翰冯诺伊曼在1945年提出。其核心思想包括: 分割(Divide):将待排序数组递归地分成两个子…...