mapStruct实体类属性映射工具实现

article/2025/6/26 1:12:44

mapStruct实体类属性映射实现

  • 1. 概述
    • 对比 BeanUtils.copyProperties
    • 场景对比
  • 快速入门
    • lombok + mapStruct
      • maven 依赖
      • 代码实现
        • 1. 定义两个要转换的实体类
        • 2. 定义转化接口
        • 测试
    • mapStruct + Spring
      • 1. 注册成bean
      • 需要使用bean
    • 转化规则
      • 1. 成员变量名不同时
      • 2. 子对象映射
      • 3. 数据类型映射
        • 自定义映射
      • 4. 多对象映射
      • 5. 原对象改造

1. 概述

MapStruct 是一个 Java Bean mapper,用于Java Bean 之间的转换。MapStruct 基于约定优于配置的设计思想,相较于常用的 BeanUtils.copyProperties 它更高效、优雅。
mapStruct 官方网站
官方文档

对比 BeanUtils.copyProperties

  1. 性能优势​
    使用 MapStruct,我们只需要定义映射接口,该库在编译时会自动生成具体实现代码, 生成的代码和手动编写的属性拷贝代码效率一致,​​适合高频调用场景​​(如高并发、大数据量)。。
  2. 类型安全与编译时检查​
  • ​​MapStruct​​:
    ​​ 强类型映射​​:在编译时检查属性名和类型是否匹配,​​避免运行时错误​​
    ​​ 自动提示错误​​:如果源对象或目标对象的属性名/类型不匹配,编译时会直接报错。
  • BeanUtils.copyProperties()​​:
    ​​ 弱类型检查​​:属性名匹配但类型不兼容时(如 String 复制到 Integer),​​运行时抛出异常​​,增加调试成本。
  1. 复杂映射支持​
  • ​​MapStruct​​:
    • ​​自定义转换逻辑​​:支持通过注解或自定义方法实现复杂类型转换(如 Date 转 String、嵌套对象映射)。
    • 多源参数映射​​:支持从多个源对象合并属性到目标对象。
    • 条件映射​​:可配置条件判断是否复制某个属性。

场景对比

​​场景​​​​MapStruct​​​​BeanUtils.copyProperties()​​
高频调用✅ 适合(高性能)❌ 不适用(反射开销大)
简单属性复制✅ 适合(但需要定义接口)✅ 适合(代码简洁)
复杂类型转换✅ 适合(支持自定义逻辑)❌ 不适用
需要编译时检查✅ 适合❌ 不适用
快速原型开发❌ 略繁琐(需定义接口)✅ 适合(快速实现)

快速入门

lombok + mapStruct

maven 依赖

  1. 映入lombok、mapStruct 依赖
  2. 必须搭配lombok-mapstruct-binding插件,以及mapStrust-process
  3. 加入path配置打包工具
<properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target><org.mapstruct.version>1.6.3</org.mapstruct.version><org.projectlombok.version>1.18.30</org.projectlombok.version><lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
</properties><dependencies><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct</artifactId><version>${org.mapstruct.version}</version></dependency><!-- lombok dependencies should not end up on classpath --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${org.projectlombok.version}</version><scope>provided</scope></dependency><!-- IntelliJ pre 2018.1.1 requires the mapstruct processor to be present as provided dependency -->
<!--        <dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>${org.mapstruct.version}</version><scope>provided</scope></dependency>--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><scope>test</scope><version>4.13.1</version></dependency></dependencies><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><!-- See https://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html --><!-- Classpath elements to supply as annotation processor path. If specified, the compiler   --><!-- will detect annotation processors only in those classpath elements. If omitted, the     --><!-- default classpath is used to detect annotation processors. The detection itself depends --><!-- on the configuration of annotationProcessors.                                           --><!--                                                                                         --><!-- According to this documentation, the provided dependency processor is not considered!   --><annotationProcessorPaths><path><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>${org.mapstruct.version}</version></path><path><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${org.projectlombok.version}</version></path><path><groupId>org.projectlombok</groupId><artifactId>lombok-mapstruct-binding</artifactId><version>${lombok-mapstruct-binding.version}</version></path></annotationProcessorPaths></configuration></plugin></plugins></pluginManagement></build>

代码实现

1. 定义两个要转换的实体类
@Data
public class SourceBo {private String test;
}@Data
public class TargetBo {private Long testing;}
2. 定义转化接口
import com.example.webapp.demos.entity.mapStruct.SourceBo;
import com.example.webapp.demos.entity.mapStruct.TargetBo;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;/*** @Description: 类型转换。* 注意:并不是只能有一个转换的方法,而是我这只写了一个。*/
@Mapper
public interface SourceTargetMapper {SourceTargetMapper MAPPER = Mappers.getMapper( SourceTargetMapper.class );@Mapping( target = "testing", source = "test" )TargetBo toTarget( SourceBo s );
}
测试
import com.example.webapp.demos.Config.SourceTargetMapper;
import com.example.webapp.demos.entity.mapStruct.SourceBo;
import com.example.webapp.demos.entity.mapStruct.TargetBo;
import org.junit.jupiter.api.Test;import static org.junit.jupiter.api.Assertions.assertEquals;/**
* @ClassName : SimpleTest  
* @Description :   
* @Date: 2025/6/2  13:27
*/public class SimpleTest {@Testpublic void testMapping() {SourceBo s = new SourceBo();s.setTest( "5" );TargetBo t = SourceTargetMapper.MAPPER.toTarget( s );assertEquals( 5, (long) t.getTesting() );}
}

嗯,没错,这样就可以了,是不是so easy!

实现原理呢,同学们可以关注一个 target 目录下生成的.class文件,它自动生成了我们定义接口的实现类。
在这里插入图片描述

如果你的代码出现和lombok的冲突问题。 官网解决方案

mapStruct + Spring

或许你会觉得使用 SourceTargetMapper.MAPPER.xxxx() 不够优雅或者与你使用spring 的bean风格有点融洽,那么你也可以将 SourceTargetMapper 注入spring 容器,来实现代码风格的统一。

1. 注册成bean

那么改动也很简单,修改@Mapper注解,设置componentModel属性值为spring。

@Mapper(componentModel = "spring")
public interface SourceTargetMapper{}

测试类

@Autowiredprivate SourceTargetMapper sourceTargetMapper;@Testpublic void testMapping() {SourceBo s = new SourceBo();s.setTest( "5" );TargetBo t = sourceTargetMapper.toTarget( s );assertEquals( 5, (long) t.getTesting() );}

需要使用bean

反过来,我们需要在mapper中引用Spring容器中的组件该如何实现? 这种情况下,我们需要改用抽象类而非接口了:

@Mapper(componentModel = "spring")
public abstract class SimpleDestinationMapperUsingInjectedService {@Autowiredprotected SimpleService simpleService;@Mapping(target = "name", expression = "java(simpleService.enrichName(source.getName()))")public abstract SimpleDestination sourceToDestination(SimpleSource source);
}

切记不要将注入的 bean 设置为private ,因为 MapStruct 需要在生成的实现类中访问该对象

编译后,代码就相当于于

@Component  // 因为 componentModel = "spring"
public class SimpleDestinationMapperUsingInjectedServiceImpl extends SimpleDestinationMapperUsingInjectedService {@Overridepublic SimpleDestination sourceToDestination(SimpleSource source) {SimpleDestination destination = new SimpleDestination();// 关键行:调用 simpleService 加工 namedestination.setName(simpleService.enrichName(source.getName()));return destination;}
}

转化规则

1. 成员变量名不同时

public class EmployeeDTO {private int employeeId;private String employeeName;// getters and setters
}public class Employee {private int id;private String name;// getters and setters
}@Mapper
public interface EmployeeMapper {@Mapping(target = "employeeId", source = "entity.id")@Mapping(target = "employeeName", source = "entity.name")EmployeeDTO employeeToEmployeeDTO(Employee entity);@Mapping(target = "id", source = "dto.employeeId")@Mapping(target = "name", source = "dto.employeeName")Employee employeeDTOtoEmployee(EmployeeDTO dto);
}

2. 子对象映射

这里我们需要为 Division 与 DivisionDTO 之间的转换添加方法。如果MapStruct检测到需要转换对象类型,并且要转换的方法存在于同一个类中,它将自动使用它。

public class EmployeeDTO {private int employeeId;private String employeeName;private DivisionDTO division;// getters and setters omitted
}public class Employee {private int id;private String name;private Division division;// getters and setters omitted
}
public class Division {private int id;private String name;// default constructor, getters and setters omitted
}public class DivisionDTO {private int Did;private String name;// default constructor, getters and setters omitted
}
public interface SourceTargetMapper {SourceTargetMapper MAPPER = Mappers.getMapper( SourceTargetMapper.class );@Mapping( target = "testing", source = "test" )TargetBo toTarget( SourceBo s );/*** * 如果写 Did 编译无法通过*/@Mapping(target = "did",source = "id")DivisionDTO toDivisionDTO( Division s );
}

这个还是有点小问题的,如果写 Did 编译无法通过

3. 数据类型映射

MapStruct还提供了一些开箱即用的隐式类型转换。本例中,我们希望将 String 类型的日期转换为 Date 对象。

有关隐式类型转换的更多详细信息,请查看 MapStruct 官方文档

public class Employee {// other fieldsprivate Date startDt;// getters and setters
}
public class EmployeeDTO {// other fieldsprivate String employeeStartDt;// getters and setters
}@Mapping(target="employeeId", source = "entity.id")
@Mapping(target="employeeName", source = "entity.name")
@Mapping(target="employeeStartDt", source = "entity.startDt",dateFormat = "dd-MM-yyyy HH:mm:ss")
EmployeeDTO employeeToEmployeeDTO(Employee entity);@Mapping(target="id", source="dto.employeeId")
@Mapping(target="name", source="dto.employeeName")
@Mapping(target="startDt", source="dto.employeeStartDt",dateFormat="dd-MM-yyyy HH:mm:ss")
Employee employeeDTOtoEmployee(EmployeeDTO dto);
自定义映射

一些特殊场景 @Mapping 无法满足时,我们需要定制化开发,同时希望保留MapStruct自动生成代码的能力。那我们可以通过创建抽象类实现。

@Mapper
abstract class TransactionMapper {public TransactionDTO toTransactionDTO(Transaction transaction) {TransactionDTO transactionDTO = new TransactionDTO();transactionDTO.setUuid(transaction.getUuid());// BigDecimal 与 long 的自定义转化transactionDTO.setTotalInCents(transaction.getTotal().multiply(new BigDecimal("100")).longValue());return transactionDTO;}public abstract List<TransactionDTO> toTransactionDTO(Collection<Transaction> transactions);
}

Collection 与 List 类型之间的转换,仍然交给 MapStruct 完成,我们只需定义接口。

又或者

@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"));}
}

4. 多对象映射

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

5. 原对象改造

    @Mapping( target = "t.testing", source = "s.test" )void toTarget( SourceBo s,@MappingTarget TargetBo t );

http://www.hkcw.cn/article/XCtrBPhaaa.shtml

相关文章

PostgreSQL 安全纵深防御:从权限到加密

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…

马斯克说不想为美政府所作一起担责 拒绝成为替罪羊

美国企业家马斯克在接受采访时表达了他对美国政府的一些看法。他表示不愿为政府所做的一切承担责任,并提到他之前领导的“政府效率部”成为了各种问题的替罪羊,这让他感到不公平。马斯克还谈及了与美国总统特朗普的关系,承认两人在一些问题上存在分歧。他强调自己不想公开反…

花105万买基金亏30万 状告银行 二审驳回全部诉求

一位年过八旬的投资者在2021年投入105万元购买了一只公募基金产品,两年多时间亏损约30万元。该投资者将相关代销银行告上法庭,要求其承担赔偿责任。案件经过两次审理,一审法院判决银行承担70%损失赔偿责任并支付损失利息。二审法院则认为,投资者自主决定购买理财产品,且产…

龙舟征婚小伙称微信被加爆 千人响应求偶遇

5月31日,广州猎德村迎来一年一度的龙舟招景盛会,超过150条村前来猎德涌趁景。其中,一条龙舟上的“征婚启事”引发广泛关注。视频显示,一名男子脖子上挂着一张写有“两栋楼,海珠,未婚”的牌子,另一面则打印了他的微信二维码。6月1日,尝试通过该微信二维码添加该男子好友…

台风失踪情况确定改变 1号台风蝴蝶六月来 生成条件逐步成熟

台风失踪情况确定改变 1号台风蝴蝶六月来 生成条件逐步成熟!2025年前五个月,西北太平洋及南海海域没有生成任何一个编号台风。从1月到5月,通常会有一到两个台风生成,有时甚至在1月或2月就会出现第一个台风。然而这一年直到6月初,海面仍然平静,这种现象让很多人感到反常。…

日本7月5日末日论"疯传:漫画预言引发恐慌

日本7月5日末日论"疯传:漫画预言引发恐慌!2025年7月5日凌晨4点18分,日本将遭遇毁灭性灾难,三分之一的国土被海水吞没。最近,这条“末日预言”在中文和日文社交平台上疯狂传播,甚至导致部分外国游客取消赴日行程。而它的源头,竟然是一本30年前的漫画。这场风波的起点…

男子称质问女子遛狗不牵绳被打耳光 涉事女子取保候审引发争议

男子称质问女子遛狗不牵绳被打耳光 涉事女子取保候审引发争议!重庆的刘先生反映,2月24日晚,他在小区质问两名女子遛狗为何不牵绳,却被其中一名女子打成轻伤二级。刘先生报警后,警方拟刑事立案,但后来女子取保候审,这让刘先生难以接受。6月2日,刘先生介绍,当晚他牵着朋…

中国斯诺克创造新的纪录 32位选手新赛季出战

6月2日,世界台联确认新赛季将有32位中国球员参加职业比赛,这一数字创下新纪录。此前,赵心童和白雨露在世锦赛中夺冠,为中国斯诺克带来新的突破。上个赛季,赵心童在克鲁斯堡强势登顶,成为首位夺得该项目世锦赛冠军的亚洲球员。世界台联为他调整规则,允许成绩纳入新赛季排…

百岁老人过寿 七旬老人捡烟花被崩伤 意外引发广泛关注

5月31日,河南平顶山鲁山县发生一起意外事件,一位7旬老人在捡拾烟花筒时头部被崩伤。此事被目击者拍摄并上传网络后引起广泛关注。老人目前仍在鲁山县人民医院重症监护室接受观察和治疗。据亲属杨先生透露,医生表示老人的右眼保不住了,脑部也受了伤,后续治疗费用预计近10万…

兰博基尼车主赖账 被代驾举报酒驾 5元停车费引发风波

兰博基尼车主赖账 被代驾举报酒驾 5元停车费引发风波。近日,浙江宁波一名兰博基尼车主酒后请代驾将车开到小区,随后自己开车进入车库,但因其不愿支付代驾小哥垫付的5元停车费,被对方举报酒驾。当地交警表示,该车主血液中的酒精含量达到酒驾标准,受到暂扣驾照6个月、扣12分…

【Oracle】索引相关

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Oracle 文章目录 1. 索引基础概述1.1 索引的作用与价值1.2 索引的工作原理1.3 索引的存储结构1.4 索引与表的关系 2. Oracle索引类型2.1 B树索引(B-Tree Index)2.1.1 B树索引结构2.1.2 创建B树索引 2.2 位图索引(Bitmap Index)2…

Dify工作流实践—根据word需求文档编写测试用例到Excel中

前言 这篇文章依赖到的操作可查阅我之前的文章&#xff1a; dify里的大模型是怎么添加进来的&#xff1a;在Windows本地部署Dify详细操作 flask 框架app.route()函数的开发和调用&#xff1a;PythonWeb开发框架—Flask工程创建和app.route使用详解 结构化提示词的编写&…

同态加密在物联网数据聚合与分析中的关键作用

在物联网&#xff08;IoT&#xff09;时代&#xff0c;智能家居设备、城市交通传感器、工业生产监测装置等各类设备如雨后春笋般涌现&#xff0c;产生了海量的数据。据国际数据公司&#xff08;IDC&#xff09;预测&#xff0c;到2025年全球物联网设备设备数量已突破 300 亿台&…

ffmpeg 的视频格式转换 c# win10

1&#xff0c;下载ffmpeg &#xff0c;并设置环境变量。 ffmpeghttps://www.gyan.dev/ffmpeg/builds/ 2.新建.net 9.0 winform using System; using System.Diagnostics; using System.Text; using System.Windows.Forms;namespace WinFormsApp11 {public partial class Fo…

[MySQL初阶]MySQL(7) 表的内外连接

标题&#xff1a;[MySQL初阶]MySQL(7)表的内外连接 水墨不写bug 文章目录 一. 内连接 (INNER JOIN)二. 外连接 (OUTER JOIN)关键区别总结 三、 如何选择 在 MySQL 中&#xff0c;连接&#xff08;JOIN&#xff09;用于根据两个或多个表之间的相关列组合行。内连接&#xff08;I…

Python自带的窗体开发技术:Tkinter 从入门到精通

文章目录 第一部分&#xff1a;Tkinter 基础1. 第一个 Tkinter 程序2. 基本组件介绍3. 布局管理pack() - 简单布局grid() - 网格布局place() - 精确位置布局 第二部分&#xff1a;Tkinter 进阶1. 事件处理2. 对话框3. 菜单和工具栏 第三部分&#xff1a;Tkinter 高级应用1. 使用…

第十七章 数据集成

系列文章目录 第一章 总体概述 第二章 在实体机上安装ubuntu 第三章 Windows远程连接ubuntu 第四章 使用Docker安装和运行EMQX 第五章 Docker卸载EMQX 第六章 EMQX客户端MQTTX Desktop的安装与使用 第七章 EMQX客户端MQTTX CLI的安装与使用 第八章 Wireshark工具的安装与使用 …

榴莲能从奢侈果变成亲民果吗 供应增加价格下降

夏季是各类水果集中上市的季节,榴莲爱好者们最近有口福了,市场上榴莲价格大幅下降,甚至出现“腰斩”的情况。这背后的原因是什么?榴莲是否会从“奢侈果”变成“亲民果”?走进浙江湖州的一家榴莲批发店,可以看到榴莲成堆地摆放在货架上,老板肖女士正在通过直播销售榴莲。…

育碧“夭折”的沙盒游戏概念图曝光:类似《我的世界》风格 创意总监离职

育碧在经历长期财务困境导致的大规模重组背景下,陆续叫停了多个项目,包括《全境封锁:郊野行动》《XDefiant》以及“Project Q”。当地时间1日,外媒MP1ST披露了育碧另一个尚未官宣但已夭折的计划——“Project Renaissance”。早在2023年,Kotaku曾报道该项目的存在。据报道…

菲律宾拉欧盟搞“安全与防务对话” 深化防务合作应对挑战

在南海紧张局势不断升级之际,菲律宾外长马纳罗与欧盟外交与安全政策高级代表卡拉斯在马尼拉召开联合记者会,宣布双方同意建立安全与防务对话机制。该机制旨在应对网络攻击、外来干预和信息操纵等跨境新兴安全威胁和挑战。安全与防务对话将以现有机制为基础,重点关注海域意识…