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

【实体转换】mapstruct详解

        大家好,我是jstart千语。今天来给大家讲讲在项目中经常可以使用得到的一个“工具”,就是mapstruct。

一、工具介绍

        这个工具有些类似于spring提供的BeanUtils.copyProperties()用于对象转化。而mapstruct是通过生成高效的、类型安全的映射代码来帮助开发人员在不同的数据对象之间进行转换。但他们的实现方式大有不同,从而他们从性能方面也会有差异。

        总得来说,如果项目中使用到的实体转换场景不多,那么使用BeanUtils就可以了,但如果项目中需要大量使用到实体转换,那么使用mapstruct在提高性能方面就是一个很好的选择

二、引入依赖

一般来说引入两个依赖,但是如果你用到了lombok依赖,一定要将lombok依赖放到mapstruct依赖前面

        <!-- lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency>        <!-- mapstruct --><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct</artifactId><version>1.4.2.Final</version></dependency><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>1.4.2.Final</version></dependency>

三、实现方法

mapstruct的实现方法有两种方式:

  • 依靠spring的依赖注入实现
  • 通过使用mapstruct静态工厂来实现

区别:

spring依赖注入更符合spring的代码风格, 支持自动注入其他 Bean。
mapstruct静态工厂简单直接,实例生命周期需手动管理

两种在性能上没有什么区别,我更喜欢用静态工厂这种方法,因为它足够简单,只需要一行代码就可以搞定。


1、spring依赖注入方式

定义Mapper接口,使用@Mapper注解并指定componentModel = "spring",使其成为Spring Bean

// Mapper 接口(配置 componentModel = "spring")
@Mapper(componentModel = "spring")
public interface JudgeSubjectConverter {JudgeSubjectDTO toDto(JudgeSubject judgeSubject);
}// 使用方式(通过注入)
@Service
public class MyService {@Autowiredprivate JudgeSubjectConverter judgeSubjectConverter;public void process() {JudgeSubject judgeSubject = new JudgeSubject();JudgeSubjectDTO judgeSubjectDTO = judgeSubjectConverter.toDto(judgeSubject);}
}

2、mapstruct静态工厂

@Mapper
public interface JudgeSubjectConverter {JudgeSubjectConverter INSTANCE = Mappers.getMapper(JudgeSubjectConverter.class);JudgeSubjectDTO toDto(JudgeSubject judgeSubject);
}// 使用方式:直接通过类名调用
@Service
public void myService(){//...JudgeSubject judgeSubject = new JudgeSubject();JudgeSubjectDTO judgeSubjectDTO = JudgeSubjectConverter.INSTANCE.toDto(judgeSubject);//...}

3、共同功能

首先记得添加@Mapper注解时,导入的是mapstruct的包,不要导入了mybatis的包了。

(1)处理字段映射

默认的映射关系就是只有属性名相同才会映射转换,如果两个实体的属性名不同还需要进行转换的话就需要手动映射。

public class User {private String email;
}public class UserDTO {private String emailAddress;
}// Mapper 接口
@Mapper
public interface UserMapper {@Mapping(source = "email", target = "emailAddress")UserDTO toDto(User user);
}

(2)忽略字段

public class User {private String password;
}public class UserDTO {private String password; // 不希望映射此字段
}// Mapper 接口
@Mapper
public interface UserMapper {@Mapping(target = "password", ignore = true)UserDTO toDto(User user);
}

(3)格式化日期

public class User {private Date createTime;
}public class UserDTO {private String createTime;
}// Mapper 接口
@Mapper
public interface UserMapper {@Mapping(target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss")UserDTO toDto(User user);
}

(4)多源对象映射

public class Order {private String orderId;
}public class User {private String userId;
}public class OrderDTO {private String orderId;private String userId;
}// Mapper 接口
@Mapper
public interface OrderMapper {@Mapping(source = "order.orderId", target = "orderId")@Mapping(source = "user.userId", target = "userId")OrderDTO toDto(Order order, User user);
}

例2:

/**
*题目信息表
*/
@Data
public class SubjectInfo implements Serializable {private static final long serialVersionUID = -71318372165220898L;/*** 主键*/private Long id;/*** 题目名称*/private String subjectName;}
---------------------------------------------------------------------------------------/**
*题目DTO
*/
@Data
public class SubjectOptionBO implements Serializable {/*** 题目答案*/private String subjectAnswer;/*** 题目选项的答案内容*/private List<SubjectAnswerBO> optionList;}
---------------------------------------------------------------------------------------
/**
*题目BO
*/
@Data
public class SubjectInfoBO extends PageInfo implements Serializable {//主键private Long id;//题目名称private String subjectName;//题目答案private String subjectAnswer;//答案选项private List<SubjectAnswerBO> optionList;}
---------------------------------------------------------------------------------------/**
* 多源类型转换
*/
@Mapper
public interface SubjectInfoConverter {SubjectInfoBO convertOptionAndInfoToBo(SubjectOptionBO subjectOptionBO,SubjectInfo subjectInfo);
}

(5)设置默认值

当源字段为 null 时,使用默认值:

@Mapping(target = "description", source = "desc", defaultValue = "暂无描述")
ProductDTO toDto(Product product);

(6)设置常量值

直接为目标字段赋固定值:

@Mapping(target = "type", constant = "VIP")
UserDTO toDto(User user);



四、与BeanUtils.copyProperties()比较

他们两个都可以进行对象转换,最主要的区别就是性能上了。

(1)Spring 的 BeanUtils.copyProperties()

底层是通过 Java 的 反射机制 实现的:

  • 运行时获取源对象和目标对象的 getter/setter 方法;
  • 检查方法签名是否匹配;
  • 动态调用 setXxx() 设置属性值。

性能影响:

  • 反射调用慢:反射比直接方法调用慢得多,因为它要解析类结构、做安全检查、动态调用方法;

  • 运行期做类型判断和匹配:每次调用都要做这些检查;

  • 不可内联优化:JVM 对反射方法优化很有限,无法像普通方法调用一样 JIT 编译优化。 


(2)MapStruct 是编译期生成代码(纯 Java)

MapStruct 在你编译代码的时候,就自动生成了普通 Java 类和方法来做对象转换,不使用反射。

  • 这些方法就是标准的 obj.setXxx(obj.getYyy());
  • 完全等同于你自己写一堆 setter 调用,只不过 MapStruct 自动帮你写好了。

性能优势:

  • 没有反射开销

  • 纯 Java 调用,JVM 可以 JIT 优化、内联优化

  • 内存更友好,因为不需要动态创建 Method 对象;

  • 执行速度接近你手写转换方法的极限。 


(3)MapStruct 字节码展示

编译完后:

相关文章:

  • 基于Flask的AI工具聚合平台技术解析
  • idea 许可证过期
  • HTML理论题
  • YOLOV4在RTX 4090 Ubuntu 24.04 LTS 下的实践总结
  • C++17 新特性简解
  • 无人机在农业中的应用与挑战!
  • 如何才能学会代数几何,代数几何的前置学科是什么
  • uniapp打包IOS私钥证书过期了,如何在非mac系统操作
  • 【项目管理】第19章 配置与变更管理-- 知识点整理
  • 观察者模式详解与C++实现
  • STM32---GPIO
  • 极狐GitLab 议题和史诗创建的速率限制如何设置?
  • 2025-04-18 李沐深度学习3 —— 线性代数
  • Windows软件界面分析软件-控件识别工具
  • echarts饼图中心呈现一张图片,并且能动态旋转的效果react组件
  • MATLAB 控制系统设计与仿真 - 35
  • YOLOv8 Bug 及解决方案汇总 【2024.1.24更新】【环境安装】【训练 断点续训】OMPError / KeyError
  • Linux根据 PID 进行性能分析
  • 【Spring Boot 源码学习】深入 ConfigurableEnvironment 的初始化过程
  • Android 13 关闭屏幕调节音量大小
  • 周继红当选中国泳协主席,曾为国摘得首枚奥运跳水金牌
  • 中越海警开展2025年第一次北部湾联合巡逻
  • 30小时已过,俄罗斯复活节停火不再延长
  • 长三角主流媒体将走进“来电”宜昌,探寻高质量发展密码
  • 儿童阅读空间、残疾人友好书店……上海黄浦如何打造城市书房
  • 在没有穹顶的剧院,和春天的音乐会来一场约会