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

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

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

相关文章

图片优化总结

1 调整大小 仅该接口&#xff1a;decodeResource 大小根据车机分辨率和图片对应的文件夹来决定的&#xff08;drawbledrawblemdpi1&#xff09; scale (float) targetDensity / density; Bitmap内存占用 ≈ 像素数据总大小 图片宽 x 图片高 x(设备分辨率/资源目录分辨率) x 每…

四个有用的Android开发技巧

一. 通过堆栈快速定位系统版本 这个地方主要分享大家两个个技巧&#xff0c;通过问题堆栈简快速定位当前系统版本&#xff1a; 1. 快速区分当前系统版本是Android10以下&#xff0c;还是Android10及以上&#xff1b; 首先Android10及以上引入了一个新的服务Service&#xff…

Go语言聊天室demo

Go语言聊天室demo 话不多说直接上代码 话不多说直接上代码 Tcp服务端 package mainimport ("container/list""encoding/json""fmt"uuid "github.com/satori/go.uuid""net""strings" )type GlobalConnInfo struc…

13年测试老鸟,接口性能测试-压测总结汇总,一文概全...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、概述 性能测试…

如何禁止员工在上班时间利用电脑打游戏逛娱乐网站?

在现代化的工作环境中&#xff0c;电脑已成为员工日常工作的必需品。然而&#xff0c;一些员工可能在上班时间利用电脑进行非工作相关的活动&#xff0c;如打游戏或浏览娱乐网站。这不仅影响个人的工作效率&#xff0c;也可能对团队的整体绩效和公司的文化产生负面影响。因此&a…

python界面开发 - Radiobutton:单选按钮

文章目录 1. python图形界面开发1.1. Python图形界面开发——Tkinter1.2. Python图形界面开发——PyQt1.3. Python图形界面开发——wxPython1.4. Python图形界面开发—— PyGTK&#xff1a;基于GTK1.5. Python图形界面开发—— Kivy1.6. Python图形界面开发——可视化工具1.7. …

前端工程部署步骤小记

安装mqtt: “mqtt”: “^4.3.7”, npm install git panjiacheng 后台demo下载zip 1、npm install --registryhttps://registry.npmmirror.com 2、npm run dev 前端demo创建 1、安装npm 2、npm install vuenext 3、npm install -g vue/cli 查看版本 vue --version 4、更新插件…

【Linux C | 网络编程】多播的概念、多播地址、UDP实现多播的C语言例子

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

如何选择适合您需求的虚拟主机服务

随着互联网的发展&#xff0c;虚拟主机服务在网站托管领域扮演着至关重要的角色。我们在前几天遇到客户咨询如何在Hostease服务商选择适合的主机服务。本文将介绍如何选择适合您需求的虚拟主机服务&#xff0c;以确保您的网站或应用程序能够稳定运行并获得最佳性能。 确定您的需…

vscode的c++开发环境配置

主要是安装一些插件&#xff0c;c/c开发常用的插件有如下几个&#xff1a; 插件名称功能C/CC 和 C的编译环境C/C SnippetsC/C重用代码块C/C Advanced LintC/C静态检测Code Runner代码运行Include AutoComplete自动头文件包含Rainbow Brackets彩虹花括号&#xff0c;有助于阅读…

常见的验证码

一、短信验证码 前端&#xff1a; 用户填写手机号&#xff0c;点击按钮发送请求用户短信得到验证码后&#xff0c;用户填写表单提交 form 表单&#xff0c;进行验证 后台&#xff1a; 随机生成几位验证码并将生成时间、手机号、验证码存储起来&#xff0c;可以存到session、…

小游戏加固方案已全面适配微信、QQ、抖音、快手、美团、华为、支付宝渠道

2023年&#xff0c;国内移动游戏收入与游戏用户规模双双创下历史新高。其中小游戏异军突起&#xff0c;市场规模达到200亿元&#xff0c;同比增长300%&#xff0c;成了万众瞩目的行业新风口。 小游戏的高速发展带来了更多的活力&#xff0c;产出了多款月流水过亿的热门游戏。行…

Linux下Qt程序打包发布

如何打包 注意事项 可能遇到的问题 如何打包&#xff1a; 1、先下载linuxdeployqt工具&#xff0c;下载链接&#xff1a;https://github.com/probonopd/linuxdeployqt/releases 选择最后release的版本&#xff0c;然后执行命令&#xff1a; sudo chmod 777 linuxdepl…

webpack编译报错Cannot find module ‘@babel/core‘且无法识别es6的reset语法

~ npm install babel-core babel-loader --save-dev 用了这个指令后webpack报错 尝试重新下载了babel-core babel-loader 还是不行 Cannot find module babel/core babel-loader8 requires Babel 7.x (the package babel/core). If youd like to use Babel 6.x (babel-core), …

Gson(List<Object>转String 、String转List<Object>)

要在Java项目中使用Gson库&#xff0c;你需要添加相应的依赖项。以下是在Maven项目的pom.xml文件中添加Gson依赖的示例&#xff1a; <dependencies><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId>&l…

Matter 笔记1-环境准备,编译

不要远程登录Ubuntu输入以下命令&#xff0c;原因&#xff1a;ubuntu/linux上的http代理设置 1. 准备 1.1 工具 Ubuntu 22.04 LTSClash 里General的端口设置到ubuntu 的网络设置里 1.2 代码 这里使用芯科整理过的代码 git clone https://github.com/SiliconLabs/matter.…

3. springboot中集成部署vue3

1. vue3构建 构建命令 npm run build&#xff0c; 构建的结果在disc目录&#xff1a; 2. springboot集成 2.1 拷贝vue3构建结果到springboot resources/static目录 2.2 springboot pom依赖 添加thymeleaf依赖 <dependency><groupId>org.springframework.boot</…

Rust错误处理和Result枚举类异常错误传递

Rust 有一套独特的处理异常情况的机制&#xff0c;它并不像其它语言中的 try 机制那样简单。 首先&#xff0c;程序中一般会出现两种错误&#xff1a;可恢复错误和不可恢复错误。 可恢复错误的典型案例是文件访问错误&#xff0c;如果访问一个文件失败&#xff0c;有可能是因…

机器学习模型—线性回归

文章目录 机器学习模型—线性回归线性回归模型的假设线性回归简单线性回归多元线性回归多元线性回归实现多项式回归多项式回归的实现线性回归二阶函数回归三阶函数回归总结机器学习模型—线性回归 线性回归是一种统计方法,用于对因变量与给定的一组自变量之间的关系进行建模。…

乔琼:高性能会议传声器的产品优化设计| 演讲嘉宾公布

一、智能家居与会议系统 智能家居与会议系统分论坛将于3月28日同期举办&#xff01; 智能会议系统它通过先进的技术手段&#xff0c;提高了会议效率&#xff0c;降低了沟通成本&#xff0c;提升了参会者的会议体验。对于现代企业、政府机构和学术界是不可或缺的。在这里&#x…