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

MapStruct 教程

MapStruct 教程

一、MapStruct 简介

MapStruct 是一个基于约定的代码生成器,用于在 Java bean 类型之间进行转换。它极大地简化了对象之间的映射过程,减少了手动编写映射代码的工作量。MapStruct 通过注解处理器在编译时生成映射代码,因此运行时性能非常好。

二、MapStruct 的使用

  1. 添加依赖

首先,你需要在项目中添加 MapStruct 的依赖。如果你使用 Maven,可以在 pom.xml 文件中添加以下依赖:

<dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct</artifactId><version>最新版本</version>
</dependency>
<dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>最新版本</version><scope>provided</scope>
</dependency>

请注意替换 <version> 标签中的内容为最新版本号。

  1. 定义 DTO(Data Transfer Object)和实体类

假设我们有一个 User 实体类和一个 UserDTO 数据传输对象。

public class User {private Long id;private String name;private String email;// getters and setters
}public class UserDTO {private Long id;private String name;// getters and setters
}
  1. 创建 Mapper 接口

接下来,我们需要创建一个 Mapper 接口,并定义映射方法。MapStruct 将为我们自动生成这个接口的实现类。

@Mapper
public interface UserMapper {UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);@Mapping(source = "id", target = "id")@Mapping(source = "name", target = "name")UserDTO userToUserDTO(User user);
}

在上面的代码中,我们使用 @Mapper 注解标记了一个 Mapper 接口,并定义了一个名为 userToUserDTO 的映射方法。@Mapping 注解用于指定源对象属性和目标对象属性之间的映射关系。在这个例子中,我们将 User 对象的 idname 属性映射到 UserDTO 对象的对应属性上。

  1. 使用 Mapper

现在我们可以使用 Mapper 来转换对象了。例如:

User user = new User();
user.setId(1L);
user.setName("John Doe");
user.setEmail("johndoe@example.com");UserDTO userDTO = UserMapper.INSTANCE.userToUserDTO(user);
System.out.println(userDTO.getId()); // 输出 1
System.out.println(userDTO.getName()); // 输出 John Doe

注意:在 Mapper 接口中定义的 INSTANCE 字段是 MapStruct 自动生成的实现类的实例。我们可以直接使用这个实例来调用映射方法。当然,你也可以通过依赖注入的方式将 Mapper 注入到你的组件中。例如,在 Spring 框架中,你可以使用 @Autowired 注解来注入 Mapper。

  1. 编译项目

最后一步是编译项目。MapStruct 将在编译时自动生成 Mapper 接口的实现类。你可以在编译后的 target/generated-sources/annotations 目录下找到生成的实现类(这个路径可能因项目配置而异)。在这个实现类中,你将看到 MapStruct 为我们生成的映射代码。这些代码通常非常简洁高效,无需我们手动编写和维护。

三、MapStruct 高级特性

  1. 自定义映射方法

当默认的映射规则不满足需求时,我们可以在 Mapper 接口中定义自定义的映射方法。例如,假设我们想要将 User 对象的 email 属性以某种方式转换后存储到 UserDTO 的一个新属性中。

首先,我们在 UserDTO 中添加一个新属性:

public class UserDTO {private Long id;private String name;private String emailHash; // 新增属性用于存储 email 的哈希值// getters and setters
}

然后,我们在 Mapper 接口中定义自定义映射方法,并使用 @Mapping 注解的 expression 属性来指定转换逻辑:

@Mapper
public interface UserMapper {UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);@Mapping(source = "id", target = "id")@Mapping(source = "name", target = "name")@Mapping(target = "emailHash", expression = "java(hashEmail(user.getEmail()))")UserDTO userToUserDTO(User user);// 自定义方法用于将 email 转换为哈希值default String hashEmail(String email) {// 这里可以使用任何哈希算法,例如 MD5、SHA-256 等// 以下为示例代码,实际应用中需要替换为真实的哈希实现return email != null ? email.hashCode() + "" : null;}
}

注意:在上面的代码中,我们使用了 default 关键字来定义了一个默认方法 hashEmail,这样我们就可以在 Mapper 接口中直接实现这个方法。然后,在 @Mapping 注解中,我们使用 expression 属性引用了这个方法。

然而,需要注意的是,直接在 @Mappingexpression 中使用自定义方法并不是 MapStruct 的推荐做法,因为这可能会使映射逻辑变得难以理解和维护。更好的做法是在 Mapper 接口中定义一个明确的映射方法,并在该方法中调用自定义逻辑。

正确的做法可能是这样的:

@Mapper
public interface UserMapper {UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);@Mapping(source = "id", target = "id")@Mapping(source = "name", target = "name")UserDTO userToUserDTO(User user);// 自定义映射方法,处理 email 的哈希转换default UserDTO userToUserDTOWithHash(User user) {UserDTO dto = userToUserDTO(user);if (user != null && user.getEmail() != null) {dto.setEmailHash(hashEmail(user.getEmail()));}return dto;}// 自定义方法用于将 email 转换为哈希值default String hashEmail(String email) {// 真实的哈希实现...return email != null ? Integer.toHexString(email.hashCode()) : null;}
}

在这个修改后的示例中,我们定义了一个默认的映射方法 userToUserDTOWithHash,它首先调用 MapStruct 生成的 userToUserDTO 方法进行基本映射,然后手动设置 emailHash 属性。这样做的好处是保持了映射逻辑的清晰和可维护性。

  1. 使用多个源对象映射到一个目标对象

有时我们可能需要将多个源对象的属性映射到一个目标对象中。MapStruct 支持这种场景,只需在 Mapper 接口中定义相应的方法即可。

例如:

@Mapper
public interface UserMapper {UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);@Mapping(source = "user.id", target = "id")@Mapping(source = "user.name", target = "name")@Mapping(source = "address.street", target = "street")@Mapping(source = "address.city", target = "city")UserDTO toUserDTO(User user, Address address);
}

在这个例子中,UserDTO 的属性可能来自 User 对象和 Address 对象。我们只需在 Mapper 接口中定义一个方法,该方法接受这两个对象作为参数,并使用 @Mapping 注解指定源属性和目标属性之间的映射关系。

  1. 处理嵌套对象和集合

MapStruct 可以很好地处理嵌套对象和集合的映射。如果源对象中包含嵌套对象或集合,并且目标对象中也有相应的嵌套对象或集合属性,那么 MapStruct 会自动递归地应用映射规则。如果需要自定义嵌套对象或集合的映射行为,我们可以在 Mapper 接口中定义相应的方法,并使用 @Mapping 注解进行配置。

  1. 使用装饰器增强映射功能

MapStruct 还支持使用装饰器来增强映射功能。装饰器是一个实现了 Mapper 接口的类,它可以在映射过程中添加额外的逻辑。装饰器类必须使用 @Decorator 注解进行标记,并且必须有一个构造方法,该构造方法接受一个实现了相同 Mapper 接口的对象作为参数。这个参数代表了被装饰的 Mapper 实现,我们可以在装饰器类中调用它的方法来执行基本的映射操作,并添加自定义的逻辑。

  1. 处理 null 值和默认值

MapStruct 提供了对 null 值和默认值的灵活处理。我们可以使用 @Mapping 注解的 nullValuePropertyMappingStrategynullValueCheckStrategydefaultValue 属性来配置 MapStruct 在遇到 null 值或默认值时的行为。例如,我们可以配置 MapStruct 在遇到 null 值时忽略映射、使用默认值进行映射或将 null 值映射为特定的值。这些配置可以帮助我们更好地控制映射过程中的数据转换和验证逻辑。

  1. 与其他框架集成

MapStruct 可以与许多流行的 Java 框架集成使用,如 Spring、JPA、Hibernate 等。在 Spring 框架中,我们可以使用 @Autowired 注解将 Mapper 注入到 Spring 管理的 Bean 中,并在业务层或控制层中直接使用它进行对象映射操作。同时,MapStruct 也支持与其他构建工具和插件集成使用,如 Maven、Gradle、Lombok 等,这可以进一步简化项目配置和代码生成过程。

四、MapStruct 集成与最佳实践

  1. 与 Spring 集成

当使用 Spring 框架时,MapStruct 可以与 Spring 无缝集成。Mapper 接口可以被 Spring 容器自动检测并作为 Bean 实例化。通过在 Mapper 接口上使用 @Mapper(componentModel = "spring"),MapStruct 会为接口生成一个 Spring Bean,这样你就可以在 Spring 管理的其他 Bean 中注入并使用它。

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

在 Spring 配置的 Bean 中,你可以像其他 Spring Bean 一样注入这个 Mapper:

@Service
public class UserService {private final UserMapper userMapper;@Autowiredpublic UserService(UserMapper userMapper) {this.userMapper = userMapper;}public UserDTO getUserDTO(User user) {return userMapper.userToUserDTO(user);}
}
  1. 与 JPA 和 Hibernate 集成

当使用 JPA 或 Hibernate 作为持久层时,你可能会遇到实体类与 DTO 之间的映射问题。MapStruct 可以帮助你轻松地将 JPA 实体映射到 DTO,反之亦然。你只需定义一个 Mapper 接口,并指定映射规则。此外,你还可以利用 MapStruct 的功能来处理 JPA 实体的延迟加载属性,以避免不必要的数据库访问。

  1. 与 Lombok 集成

Lombok 是一个流行的 Java 库,用于减少样板代码,如 getters、setters 和构造函数。当使用 Lombok 时,确保 MapStruct 的注解处理器在 Lombok 的注解处理器之后运行是很重要的。这样,MapStruct 可以正确地识别 Lombok 生成的 getter 和 setter 方法。在 Maven 或 Gradle 构建配置中,你可以通过调整注解处理器的顺序来实现这一点。

  1. 最佳实践

    • 保持 Mapper 接口简洁:尽量避免在 Mapper 接口中定义复杂的业务逻辑。Mapper 的主要责任是执行对象之间的简单映射。如果需要更复杂的逻辑,考虑在服务层中处理。
    • 使用有意义的映射方法名称:为映射方法选择描述性的名称,以清晰地表达它们的意图。
    • 编写单元测试:为 Mapper 接口编写单元测试,以确保映射逻辑的正确性。这可以通过使用 JUnit 和 AssertJ(或类似库)来实现。
    • 注意性能:虽然 MapStruct 本身的性能很好,但在处理大量数据时仍需注意。尽量避免在循环中调用 Mapper 方法,而是尝试批量映射数据。
    • 遵循 DRY 原则:避免重复定义相同的映射规则。如果多个 Mapper 接口需要相同的映射逻辑,考虑使用共享配置或抽象 Mapper 接口。
    • 及时更新 MapStruct 版本:MapStruct 是一个活跃的项目,经常发布新版本以修复错误并添加新功能。保持与最新版本的同步,以便利用最新的改进和功能。

五、MapStruct 高级特性

  1. 自定义映射方法

当默认的映射规则不能满足需求时,MapStruct 允许你自定义映射方法。通过在 Mapper 接口中定义一个带有 @Mapping 注解的方法,并指定自定义的映射逻辑,你可以覆盖默认的映射行为。

@Mapper
public interface UserMapper {@Mapping(target = "userName", source = "user.name")@Mapping(target = "userAge", source = "user.age")UserDTO toUserDTO(User user);default String mapName(String name) {// 自定义映射逻辑return name != null ? name.toUpperCase() : null;}
}

在上面的例子中,mapName 方法是一个自定义的映射方法,它将用户名转换为大写。然而,请注意,自定义方法必须是接口的默认方法(使用 default 关键字),并且 MapStruct 目前不支持直接在 @Mapping 注解中引用自定义方法。通常,自定义映射方法用于更复杂的转换逻辑,可能需要结合 @AfterMapping@BeforeMapping 使用。

但是,上面的例子实际上是不正确的,因为 MapStruct 不支持直接在 @Mapping 中使用自定义方法。正确的做法是使用 @AfterMapping@BeforeMapping,或者在生成的实现类中覆盖方法。下面是一个使用 @AfterMapping 的例子:

@Mapper
public interface UserMapper {@Mapping(target = "userName", source = "name")@Mapping(target = "userAge", source = "age")UserDTO toUserDTO(User user);@AfterMappingdefault void afterToUserDTO(User user, @MappingTarget UserDTO userDTO) {if (userDTO.getUserName() != null) {userDTO.setUserName(userDTO.getUserName().toUpperCase());}}
}

在这个修正后的例子中,afterToUserDTO 方法会在 toUserDTO 方法之后被调用,以应用额外的映射逻辑。

  1. 使用表达式进行映射

MapStruct 允许你在 @Mapping 注解中使用表达式来指定更复杂的映射规则。例如,你可以使用 Java 表达式语言(Expression Language)或 Spring 表达式语言(Spring Expression Language)来执行条件映射或计算。

然而,请注意,直接在 @Mapping 中使用表达式的功能并不是 MapStruct 核心库的一部分。这可能是一个误解或混淆了其他库的功能。MapStruct 主要依赖于其注解处理器在编译时生成映射代码,而不是在运行时解析表达式。因此,上面的说法需要修正。实际上,你应该使用自定义方法或生命周期回调(如 @AfterMapping)来处理复杂的映射逻辑。

  1. 处理集合映射

MapStruct 可以很容易地处理集合类型的映射。只需在 Mapper 接口中定义一个返回集合类型的方法,MapStruct 就会自动生成相应的映射代码。你还可以使用 @IterableMapping 注解来自定义集合映射的行为。

@Mapper
public interface UserMapper {UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);@Mapping(target = "userName", source = "name")@Mapping(target = "userAge", source = "age")UserDTO toUserDTO(User user);List<UserDTO> toUserDTOList(List<User> users);
}

在这个例子中,toUserDTOList 方法会自动将 List<User> 映射为 List<UserDTO>。MapStruct 会为列表中的每个元素调用 toUserDTO 方法。你还可以通过在 @IterableMapping 注解中指定其他选项来自定义集合映射的行为(尽管在这个例子中并没有使用 @IterableMapping)。然而,请注意 @IterableMapping 实际上并不是 MapStruct 的一部分;上面的代码已经足够处理集合的映射了。如果你想对集合映射进行更细粒度的控制(例如过滤或排序),你可能需要在服务层中实现这些逻辑。

  1. 使用装饰器增强 Mapper

MapStruct 支持装饰器模式,允许你在不修改生成的 Mapper 实现的情况下增强 Mapper 的功能。通过定义一个实现了 Mapper 接口的类,并在该类中添加额外的映射逻辑,你可以扩展 Mapper 的行为。然后,你可以通过配置 MapStruct 来使用这个装饰器类而不是直接生成的 Mapper 实现。这对于添加日志记录、性能监控或事务管理等横切关注点非常有用。然而,请注意装饰器模式在 MapStruct 中的具体实现可能因版本而异,并且可能需要额外的配置步骤来启用它。在某些情况下,使用 @DecoratedWith 注解可以指定一个装饰器类来增强 Mapper 的功能。但是,请注意这个特性的具体细节和可用性可能会随着 MapStruct 版本的变化而变化。建议查阅最新的 MapStruct 文档以获取关于如何使用装饰器的准确信息。

总的来说,MapStruct 是一个功能强大的代码生成器,可以大大简化对象映射的工作。通过合理地使用其提供的注解和特性,你可以编写出清晰、高效且易于维护的映射代码。

相关文章:

  • 【Java面试题】SpringBoot与Spring的区别
  • Programming Abstractions in C阅读笔记:p308-p311
  • 暗九之凶险,更甚于明九
  • K8S部署postgresql
  • Node.js_基础知识(CommonJS模块化)
  • Hololens 2应用开发系列(1)——使用MRTK在Unity中设置混合现实场景并进行程序模拟
  • 23端口登录的Telnet命令+传输协议FTP命令
  • Django 表单
  • 【Git】深入理解 Git 分支合并操作:git merge dev 命令详解
  • 2024年,智慧文旅领航新时代,重塑旅行体验的未来篇章!
  • oppo手机备忘录记录怎么转移到华为手机?
  • wordpress 开源主题
  • Linux 开发工具vim、gcc/g++、makefile
  • TypeScript08:在TS中使用模块化
  • AGI概念与实现
  • [Vue CLI 3] 配置解析之 css.extract
  • Android 控件背景颜色处理
  • Bootstrap JS插件Alert源码分析
  • JavaScript 一些 DOM 的知识点
  • java架构面试锦集:开源框架+并发+数据结构+大企必备面试题
  • JS学习笔记——闭包
  • Laravel深入学习6 - 应用体系结构:解耦事件处理器
  • LeetCode算法系列_0891_子序列宽度之和
  • magento 货币换算
  • miaov-React 最佳入门
  • MQ框架的比较
  • Promise面试题,控制异步流程
  • puppeteer stop redirect 的正确姿势及 net::ERR_FAILED 的解决
  • quasar-framework cnodejs社区
  • Vue.js源码(2):初探List Rendering
  • Vue小说阅读器(仿追书神器)
  • 基于webpack 的 vue 多页架构
  • 计算机常识 - 收藏集 - 掘金
  • 力扣(LeetCode)22
  • 批量截取pdf文件
  • 文本多行溢出显示...之最后一行不到行尾的解决
  • 我与Jetbrains的这些年
  • 线上 python http server profile 实践
  • 用jQuery怎么做到前后端分离
  • 在Docker Swarm上部署Apache Storm:第1部分
  • 整理一些计算机基础知识!
  • ​Z时代时尚SUV新宠:起亚赛图斯值不值得年轻人买?
  • #控制台大学课堂点名问题_课堂随机点名
  • (17)Hive ——MR任务的map与reduce个数由什么决定?
  • (C++20) consteval立即函数
  • (Matalb回归预测)PSO-BP粒子群算法优化BP神经网络的多维回归预测
  • (python)数据结构---字典
  • (附源码)springboot家庭装修管理系统 毕业设计 613205
  • (十)DDRC架构组成、效率Efficiency及功能实现
  • (四)Tiki-taka算法(TTA)求解无人机三维路径规划研究(MATLAB)
  • (转)Unity3DUnity3D在android下调试
  • (自适应手机端)响应式新闻博客知识类pbootcms网站模板 自媒体运营博客网站源码下载
  • .mkp勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .NET 动态调用WebService + WSE + UsernameToken
  • .NET 反射 Reflect