MapStruct用法和实践

一、MapStruct 用法

1. 嵌套对象深度映射(Deep Mapping)

// 源对象
public class User {private Address address;// getter/setter
}public class Address {private String city;private String street;
}// 目标对象
public class UserDTO {private String city;      // 直接映射 user.address.cityprivate String fullAddr;  // user.address.city + street
}@Mapper
public interface UserMapper {@Mapping(target = "city", source = "address.city")@Mapping(target = "fullAddr", source = "address")UserDTO toDTO(User user);// 自定义组合逻辑default String addressToFullAddr(Address address) {if (address == null) return null;return (address.getCity() != null ? address.getCity() : "") + " " +(address.getStreet() != null ? address.getStreet() : "");}
}

✅ 自动生成:

dto.setCity(user.getAddress().getCity());
dto.setFullAddr(addressToFullAddr(user.getAddress()));

2. 集合映射(List/Set/Map ↔ List/Set/Map)

@Mapper
public interface OrderMapper {List<OrderDTO> toDTOs(List<Order> orders);Set<ProductDTO> toProductDTOs(Set<Product> products);Map<String, String> toStringMap(Map<String, Object> map); // 需自定义转换
}

✅ 自动生成遍历逻辑,无需手动 stream().map().collect()

自定义集合元素转换:
@Mapper(uses = CustomConverters.class)
public interface OrderMapper {List<OrderSummaryDTO> toSummaries(List<Order> orders);
}@Component
public class CustomConverters {public String formatAmount(BigDecimal amount) {return amount == null ? "0.00" : amount.setScale(2).toString();}
}

3. 双向映射(Bidirectional Mapping)

@Mapper
public interface UserMapper {// 正向@Mapping(target = "birthDate", dateFormat = "yyyy-MM-dd")UserDTO toDTO(User user);// 反向(自动推断)@InheritInverseConfiguration@Mapping(target = "id", ignore = true) // 创建时忽略 IDUser fromDTO(UserDTO dto);
}

🔁 @InheritInverseConfiguration 自动继承正向配置,反向映射。


4. 更新现有对象(Update Mapping)

常用于 PUT/PATCH 接口更新实体。

@Mapper
public interface UserMapper {@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)void updateUser(@MappingTarget User user, UserDTO dto);
}

✅ 仅拷贝非 null 字段,避免覆盖原有值。


5. 条件映射(Conditional Mapping)

@Mapper
public interface UserMapper {@Mapping(target = "status", source = "status", qualifiedByName = "statusToCode")UserDTO toDTO(User user);@Named("statusToCode")default String mapStatus(UserStatus status) {if (status == null) return null;return switch (status) {case ACTIVE -> "A";case INACTIVE -> "I";default -> "U";};}
}

qualifiedByName 指定使用哪个自定义方法。


6. 多源映射(Multi-Source Mapping)

@Mapper
public interface UserDetailMapper {@Mapping(target = "userName", source = "user.name")@Mapping(target = "roleName", source = "role.name")@Mapping(target = "deptName", source = "dept.title")UserDetailDTO toDTO(User user, Role role, Department dept);
}

✅ 适用于 VO 聚合多个 Entity 的场景。


7. Map 与 Object 映射

@Mapper
public interface ConfigMapper {@MapMappingMap<String, String> toStringMap(Config config);@MapMappingConfig toConfig(Map<String, String> map);
}

✅ 自动生成 get/set 映射。


8. Builder 模式支持(Lombok @Builder)

@Mapper(builder = @Builder(disableBuilder = false))
public interface UserMapper {UserDTO toDTO(User user); // 自动调用 UserDTO.builder().name(...).build()
}

✅ 支持不可变对象构建。


9. 泛型映射(Generic Mapping)

@Mapper
public interface GenericMapper {<T, R> List<R> mapList(List<T> source, Class<R> targetClass);
}

⚠️ 泛型擦除问题:需在运行时传入 Class 对象。


10. 生命周期回调(Lifecycle Callbacks)

@Mapper
public abstract class UserMapper {@BeforeMappingprotected void before(User user, @MappingTarget UserDTO dto) {System.out.println("开始映射: " + user.getId());}@AfterMappingprotected void after(@MappingSource User user, @MappingTarget UserDTO dto) {dto.setAgeGroup(calculateAgeGroup(user.getAge()));}public abstract UserDTO toDTO(User user);private String calculateAgeGroup(int age) {return age < 18 ? "CHILD" : age < 60 ? "ADULT" : "SENIOR";}
}

✅ 适用于审计、日志、后处理逻辑。


二、MapStruct 与 DDD(领域驱动设计)融合实践

1. 实体 ↔ DTO 转换分层设计

Web Layer:     OrderRequest  → OrderDTO
Application:   OrderDTO     → Command
Domain:        Command      → Order (Entity)
Persistence:   Order        → OrderEntity (JPA)
映射层划分:
  • WebMapper:Request/Response ↔ DTO
  • ApplicationMapper:DTO ↔ Command/Event
  • PersistenceMapper:Entity ↔ JPA Entity
@Mapper(componentModel = "spring")
public interface WebOrderMapper {@Mapping(target = "customerId", source = "customer.id")OrderDTO toDTO(CreateOrderRequest request);
}

2. 值对象(Value Object)映射

public class Address {private String street;private String city;// 不可变
}@Mapper
public interface AddressMapper {@Mapping(target = "street", source = "street")@Mapping(target = "city", source = "city")Address createAddress(String street, String city);
}

✅ 使用 @Context 或工厂方法创建 VO。

三、MapStruct 企业级配置与最佳实践

1. 全局配置(@MapperConfig)

@MapperConfig(componentModel = "spring",nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE,nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS,unmappedTargetPolicy = ReportingPolicy.WARN,uses = { DateMapper.class, CustomConverters.class }
)
public class BaseMappingConfig {}

然后所有 Mapper 继承:

@Mapper(config = BaseMappingConfig.class)
public interface UserMapper { ... }

2. Spring 集成(推荐方式)

@Mapper(componentModel = "spring")
public interface UserMapper {UserDTO toDTO(User user);
}

✅ 自动生成 Spring Bean,可直接 @Autowired


3. 错误策略配置

@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR)
public interface UserMapper { ... }
  • IGNORE:忽略未映射字段(默认)
  • WARN:编译警告
  • ERROR:编译报错(推荐用于生产)

4. 空值处理策略

策略说明
NullValuePropertyMappingStrategy.SET_TO_NULL源为 null 时,目标也设为 null
IGNORE忽略 null 值(常用于更新)
SET_TO_DEFAULT设为默认值(如 0, false)

四、MapStruct 与主流框架集成

1. Spring Boot(推荐 starter)

<dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-spring-extensions</artifactId><version>1.0.0.CR1</version>
</dependency>

支持 @Mapper SpringBean 自动注入。


2. Lombok 集成(避免冲突)

问题:Lombok 和 MapStruct 都是 APT,处理顺序冲突。
解决方案 1:Maven 配置 processor 顺序
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><annotationProcessorPaths><path><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${lombok.version}</version></path><path><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>${mapstruct.version}</version></path></annotationProcessorPaths></configuration>
</plugin>
解决方案 2:使用 lombok.config
# lombok.config
lombok.addLombokGeneratedAnnotation = true

3. Quarkus / GraalVM 原生镜像

  • MapStruct 生成的代码无反射,天然支持原生编译
  • 无需额外配置反射规则。

五、MapStruct 调试与问题排查

1. 查看生成的代码

  • Maven 项目:target/generated-sources/annotations/
  • 手动编译:-s 指定生成目录

2. 常见编译错误

错误原因解决
Can't map property ...字段不存在或类型不匹配使用 @Mapping(ignore=true) 或自定义转换
Ambiguous mapping methods多个转换方法匹配使用 @Named + qualifiedByName
No property named ...拼写错误检查 getter/setter

六、MapStruct 性能优化建议

  1. 复用 Mapper 实例(Spring Bean)
  2. 避免在循环中创建 MapperMappers.getMapper() 有反射开销)
  3. 使用 @InheritConfiguration 减少重复配置
  4. 禁用不必要的空值检查(如已知非空)

七、真实企业架构案例

场景:电商平台订单详情聚合

@Mapper(uses = {DateMapper.class, PriceFormatter.class})
public interface OrderDetailMapper {@Mapping(target = "customerName", source = "customer.name")@Mapping(target = "productName", source = "product.name")@Mapping(target = "totalPrice", source = "order.total", qualifiedByName = "formatPrice")@Mapping(target = "items", source = "orderItems")OrderDetailView toView(Order order, Customer customer, Product product);List<OrderItemDTO> toItemDTOs(List<OrderItem> items);
}

✅ 一行代码聚合三个服务的数据。


八、总结:MapStruct 使用 checklist

✅ 是否使用 @Mapper(componentModel = "spring")
✅ 是否配置 unmappedTargetPolicy = ERROR
✅ 是否使用 @InheritInverseConfiguration 减少重复?
✅ 是否避免在循环中调用 Mappers.getMapper()
✅ 是否为日期、枚举等配置了自定义转换器?
✅ 是否在 pom.xml 正确配置了 annotationProcessorPaths?
✅ 是否检查了生成的代码是否符合预期?


九、附录:MapStruct 常用注解速查表

注解用途
@Mapper定义映射接口
@Mapping字段级映射配置
@Mappings多个 @Mapping
@InheritConfiguration继承配置
@InheritInverseConfiguration反向继承
@BeanMapping方法级配置(如 null 策略)
@AfterMapping / @BeforeMapping生命周期回调
@Context传递上下文对象
@Named标记转换方法
@Qualifier自定义限定符

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

设计模式相关面试题

写在前面 &#x1f525;我把后端Java面试题做了一个汇总&#xff0c;有兴趣大家可以看看&#xff01;这里&#x1f449; ⭐️在反复复习面试题时&#xff0c;我发现不同资料的解释五花八门&#xff0c;容易造成概念混淆。尤其是很多总结性的文章和视频&#xff0c;要么冗长难…

访问者设计模式

访问者设计模式是一种行为模式&#xff0c;允许您向现有对象结构添加新作&#xff0c;而无需修改其类。 它通过允许您将算法与其作的对象分开来实现这一点。 它在以下情况下特别有用&#xff1a; 您有一个复杂的对象结构&#xff08;如 AST、文档或 UI 元素&#xff09;&#x…

Linux_用 `ps` 按进程名过滤线程,以及用 `pkill` 按进程名安全杀进程

用 ps 按进程名过滤线程&#xff0c;以及用 pkill 按进程名安全杀进程摘要&#xff1a; 过滤线程信息&#xff1a;教你用 ps -C、pgrepps 等多种姿势&#xff0c;既精准又避免误杀。按名字杀进程&#xff1a;用 pkill 一把梭&#xff0c;优雅还是强杀随你选&#xff0c;附带“先…

关于国产 RAC 和分布式研讨

本次研讨核心目标是围绕崖山 DB、达梦 DB、GBASE三款国产数据库&#xff0c;以及数据库内核开发吕工程师的分享&#xff0c;深入了解共享集群 RAC 的开发技术。但实际效果未达预期&#xff0c;参会者多围绕 “共享集群与分布式应用场景” 泛泛而谈&#xff0c;缺乏深度技术拆解…

传输层协议介绍

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档文章目录前言一、TCP协议介绍二、TCP报文格式三、TCP三次握手四、TCP四次挥手五、UDP协议介绍六、常见协议及其端口七、TCP与UDP的不同总结前言提示&#xff1a;这里可以添加本…

Vibe Coding 概念提出者 AndrejKarpathy 谈强化学习。

在预训练时代&#xff0c;关键在于互联网文本。你最需要的是一大批量、多样化且高质量的互联网文档&#xff0c;供模型从中学习。在监督微调&#xff08;SFT&#xff09;时代&#xff0c;核心则是对话数据。人们雇佣合同工人为问题撰写答案&#xff0c;类似于你在 Stack Overfl…

OSI模型和TCP/IP模型区别是什么

问题OSI模型和TCP/IP模型区别是什么我的回答OSI和TCP/IP这两个协议栈有几个主要区别&#xff1a;首先&#xff0c;层次结构不同。OSI是七层模型&#xff1a;物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。而TCP/IP是四层模型&#xff1a;数据链路层、网络层、传…

ros2与gazebo harmonic机械臂仿真项目Moveit2YoloObb的优化

文章目录 关于项目RVIZ控制Gazebo Harmonic仿真机械臂GraphExecuter创建流程并通过Yolo算法抓取螺栓 关于项目 本文介绍ros2与gazebo harmonic机械臂仿真项目Moveit2YoloObb优化的内容&#xff0c;具体的代码细节就不赘述了&#xff0c;主要还是演示效果&#xff0c;包括RVIZ控…

Linux 系统调优与CPU-IO-网络内核参数调优

1. Linux系统调优1.1 安装工具包在开始监控前&#xff0c;需要确保系统已安装以下工具包&#xff0c;它们是后续操作的基础&#xff1a;sysstat&#xff1a;包含 mpstat、iostat、sar 等核心统计工具iotop&#xff1a;专门监控磁盘 I/O 的进程级工具nethogs&#xff1a;按进程查…

laravel学习并连接mysql数据库,给本地vue项目提供接口

下载laravel laravel下载地址phpstudy_pro\WWW\laravel.env文件 DB_CONNECTIONmysql DB_HOST127.0.0.1 DB_PORT3306 DB_DATABASEclgl //你的数据库名称 DB_USERNAMEroot //你的账号 DB_PASSWORDroot //你的密码安装 Laravel CORS 包 composer require fruitcake/laravel-c…

Mybatis 与 Springboot 集成过程详解

Mybatis 与 Springboot 集成过程详解一. 核心概念与优势二.Mybatis 核心类简介1.MybatisAutoConfiguration2.MapperScans3.MapperScannerRegistrar4.MapperFactoryBean5.Configuration6.MapperRegistry7.MapperProxy 与 MapperProxyFactory7.1核心定位与职责7.22. ​​MapperPr…

prometheus alertmanager 对接飞书

alertmanager 直接配置 飞书 的 webhook &#xff0c;发现并不满足飞书接口的 json 格式。报错如下levelerror ts2025-08-28T04:57:02.734Z callerdispatch.go:310 componentdispatcher msg"Notify for alerts failed" num_alerts23 err"prometheusalert-webhoo…

『专利好药用力心脑血管健康』——爱上古中医(28)(健康生活是coder抒写优质代码的前提条件——《黄帝内经》伴读学习纪要)

心脏血管三通康&#xff0c;古时丸药精益装。 笔记模板由python脚本于2025-08-26 18:25:03创建&#xff0c;本篇笔记适合喜欢日常保健养生知识的coder翻阅。 学习的细节是欢悦的历程 博客的核心价值&#xff1a;在于输出思考与经验&#xff0c;而不仅仅是知识的简单复述。 Pyth…

在 .NET 8.0 中实现 JWT 刷新令牌

介绍在 Web 开发领域&#xff0c;安全是重中之重。JSON Web Tokens (JWT) 已成为在各方之间安全传输信息的热门选择。然而&#xff0c;在 JWT 过期后&#xff0c;如何维护用户会话并避免频繁登录至关重要。这正是 JWT 刷新令牌应运而生的地方。在本文中&#xff0c;我们将指导您…

深入解析 git push 命令

1. 基础语法 git push 的基本语法如下: git push <远程仓库名> <本地分支名>:<远程分支名> [选项]<远程仓库名>: 通常是 origin(默认的远程仓库名称)。 <本地分支名>:<远程分支名>: 指定要推送的本地分支以及目标远程分支。如果省略远…

UI弹出动画

简介的UI弹出动画 使用方式很简单 挂载到需要弹出的目标 即可 using UnityEngine; using DG.Tweening; using Unity.VisualScripting;/// <summary>/// 简洁的UI动画脚本/// 直接挂载到UI组件上&#xff0c;调用Play()播放缩放弹出动画/// </summary>public class …

PostgreSQL诊断系列(6/6):配置项全景解析——打造你的专属优化清单

&#x1f517; 作为《PostgreSQL诊断系列》的收官之作&#xff0c;今天我们系统梳理 postgresql.conf 中的核心参数&#xff0c;将前5篇的“诊断”转化为“调优”&#xff0c;打造一套生产环境专属的配置模板。 你是否&#xff1a; 不知道哪些参数该调&#xff1f;害怕调错导致…

Flink Slot 不足导致任务Pending修复方案

当前有3个虚拟机节点&#xff0c;每个节点配置的slot节点数量是4&#xff0c;${FLINK_HOME}/conf/flink-conf.yaml 关于slot的配置如下&#xff1a; # The number of task slots that each TaskManager offers. Each slot runs one parallel pipeline. taskmanager.numberOfTas…

亚马逊合规风控升级:详情页排查与多账号运营安全构建

2025年亚马逊掀起的大规模扫号行动&#xff0c;聚焦商品详情页合规性审查&#xff0c;标志着跨境电商合规监管进入严风控时代&#xff0c;此次行动以关键词规范与定价诚信为核心&#xff0c;大量卖家因内容违规遭遇账号停用&#xff0c;对于卖家而言&#xff0c;构建系统化的合…

FISCO-BCOS-Python 模板

基于Python-SDK的FISCO BCOS区块链HelloWorld模板&#xff0c;提供了简单的问候语设置和查询功能。本项目采用现代Python开发实践&#xff0c;包含完整的配置管理、测试框架和项目结构。 快速开始 仓库地址&#xff1a;git clone https://gitee.com/atanycosts/python-fisco-te…