目录
一、前言
二、实体对象映射概述
2.1 什么是实体对象映射
2.1.1 ORM的几个基本概念
2.1.2 ORM常用的框架
2.2 实体对象映射存在的问题
2.2.1 映射配置错误
2.2.2 性能问题
2.2.3 修改字段不一致问题
三、实体对象属性拷贝工具概述
3.1 什么是实体对象属性拷贝工具
3.2 实体对象属性拷贝工具的好处
3.3 常用的实体对象属性拷贝工具
四、MapStruct 介绍
4.1 MapStruct 是什么
4.2 MapStruct 主要特点和优势
4.3 MapStruct 使用流程
五、MapStruct 使用案例操作演示
5.1 导入依赖
5.2 MapStruct 基础案例
5.2.1 提供一个源对象和目标对象
5.2.2 提供mapstruct自定义映射接口
5.2.3 测试类
5.2 MapStruct 字段映射转换
5.2.1 差异化字段映射操作
5.2.2 忽略字段映射
5.2.3 给属性赋默认值
5.2.4 引用对象作为内部属性赋值
5.2.5 映射对象属性更新
5.3 MapStruct 数据类型转换
5.3.1 int 与string类型的转换
5.3.2 string类型与enum类型转换
5.3.3 格式化显示
5.3.4 时间类型格式化显示
5.4 MapStruct 集合类型映射
5.4.1 Map与Bean的映射
5.4.2 集合数据类型的转换
5.4.3 集合拷贝
5.5 MapStruct 自定义字段映射
5.5.1 定义源对象和目标对象
5.5.2 定义对应映射方法接口
5.5.3 定义转换器的抽象类
5.5.4 测试代码
5.5.5 测试效果
六、springboot 整合MapStruct
6.1 前置准备
6.2 整合详细过程
6.2.1 提供源对象和目标对象
6.2.2 MapStruct 对象映射接口
6.2.3 测试接口
七、写在文末
一、前言
在日常项目开发中,尤其是在rest接口开发时,在实际返回的结果中展示的字段,在工程代码中,从接口类到底层查询数据库,中间其实经历了很多层值对象的映射,拷贝,转换,最终将特定的数据库字段经过处理之后返回,在这个过程中,对象属性的拷贝想必做过微服务开发的同学应该不陌生,虽然看似是很简单的一步操作,但是如果使用不好,或者没有使用正确的组件,带来的影响也是很严重的,本文将详细讨论下这个问题。
二、实体对象映射概述
2.1 什么是实体对象映射
在Java中,"实体对象映射"(Object Relational Mapping, ORM)是一种编程技术,用于将面向对象模型中的对象与关系型数据库中的表格进行映射。ORM的核心目标是让开发人员能够以面向对象的方式操作数据库,而不需要编写复杂的SQL语句。
2.1.1 ORM的几个基本概念
ORM的基本概念包括:
-
实体 (Entity): 实体代表了数据库中的表,在Java中通常是一个类,每个实例代表数据库表中的一行记录。
-
属性 (Property): 实体中的每个属性对应数据库表中的一个字段。
-
关系 (Relationship): 实体之间可以通过一对多、多对一或多对多的关系相互关联,这些关系在数据库中通过外键实现。
2.1.2 ORM常用的框架
在使用springboot进行微服务开发中,常见的ORM框架包括:
-
Hibernate
-
Hibernate是最流行且功能强大的ORM框架之一。
-
它提供了完整的ORM解决方案,支持多种数据库和复杂的查询。
-
还支持懒加载、缓存策略以及事务管理等高级特性。
-
-
MyBatis
-
MyBatis是一个半自动的ORM框架,它允许开发者直接编写SQL语句并将其映射到Java对象。
-
MyBatis非常适合那些需要对SQL有更多控制的应用程序。
-
它提供了一个简单的XML配置文件来定义映射规则,也可以使用注解来定义映射。
-
-
JPA
-
JPA是一个Java标准,定义了一组用于持久化Java对象的标准接口。
-
许多ORM框架实现了JPA规范,如Hibernate、EclipseLink等。
-
JPA提供了一种声明式的数据访问方法,简化了实体对象与数据库之间的映射。
-
-
MyBatis Plus
-
是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生;
-
它不仅仅是一个 ORM 框架,更是一个能够帮助开发者快速完成 CRUD 操作的工具;
-
它提供了大量的实用功能,包括但不限于自动 CRUD、通用 Mapper、分页插件、性能分析插件等
-
2.2 实体对象映射存在的问题
尽管使用ORM框架可以为开发过程中带来很多便利,比如使用mybatis-plus可以减少很多常规sql的编写,但是仍然存在一些不可忽视的问题。
2.2.1 映射配置错误
这是一种非常常见的问题,具体来说主要表现为:
-
字段映射不匹配:实体类中的字段名与数据库表中的列名不匹配,导致数据无法正确存取。
-
这个在程序运行过程中表现为直接抛错;
-
以mybatis为例,xml文件中映射的实体对象属性必须要一一对应上才可以;
-
-
类型转换问题:数据库中的数据类型与 Java 实体类中的数据类型不匹配,例如日期类型、枚举类型等的处理不当,就会造成程序抛错。
-
忽略外键约束:在实体类中没有正确处理外键关系,可能导致数据完整性问题。
2.2.2 性能问题
在使用ORM框架时可能造成性能问题的点包括:
-
N+1 查询问题
-
当从数据库获取主对象时,如果需要额外加载关联的对象,这可能会导致多个额外的查询,从而产生 N+1 查询问题。
-
懒加载与急加载冲突:懒加载(lazy loading)是指在真正需要时才加载关联对象,而急加载(eager loading)是在主对象加载时同时加载所有关联对象。不正确的设置可能导致不必要的数据加载或者延迟加载带来的性能开销。
-
过度查询:有时为了获取特定的信息,可能会查询过多的数据,导致网络传输和内存消耗增加。
-
2.2.3 修改字段不一致问题
如果业务中需要接口增加新的返回值属性,那么映射的对象,设置对象值的所有业务逻辑均需要重新修改,必须在下面的这段逻辑中,如果数据库的字段名称调整了,那么类似的代码均要手动调整
三、实体对象属性拷贝工具概述
3.1 什么是实体对象属性拷贝工具
实体对象属性拷贝工具是指那些专门用来复制一个对象的属性到另一个对象中的工具。这些工具通常用于简化对象间的属性复制,特别是在需要将一个对象的状态转移到另一个对象的情况下。
比如在上面的这段代码中,如果没有实体对象属性拷贝工具的话,将user对象的属性拷贝到userVO中,就需要手动一个个赋值,而使用了对象拷贝工具,只需要下面一段代码即可完成:
public UserVO getByUserId(int userId) {
User user = userMapper.getById(userId);
UserVO userVO = new UserVO();
BeanUtils.copyProperties(user,userVO);
return userVO;
}
3.2 实体对象属性拷贝工具的好处
实体对象属性拷贝工具在软件开发中非常有用,它们能够帮助开发者更高效地管理和操作数据。以下是使用这类工具的一些主要好处:
-
减少代码量
-
属性拷贝工具可以显著减少手动编写属性复制逻辑所需的代码量,从而避免了大量的重复代码。
-
-
提高代码可读性,且易于维护:
-
通过使用专门的工具,代码变得更简洁,易于阅读和维护。
-
当实体对象的结构发生变化时,只需要更新配置或少量代码即可,而无需修改大量的拷贝逻辑。
-
-
减少出错机会:
-
手动复制属性容易出错,比如遗漏某些字段或者写错字段名。属性拷贝工具可以自动完成这个过程,降低错误率。
-
-
提高开发效率:
-
开发者可以将更多的时间集中在业务逻辑上,而不是繁琐的数据拷贝任务上。
-
-
支持复杂映射:
-
某些工具支持复杂的映射逻辑,例如字段重命名、类型转换等。
-
-
性能优化:
-
一些高级工具(如 MapStruct)通过生成专用的映射器类来提高映射效率,这比使用反射机制要快得多。
-
-
易于集成
-
大多数属性拷贝工具都设计得易于集成到现有的项目中,无论是独立应用还是大型框架。
-
3.3 常用的实体对象属性拷贝工具
下面是一些常用的实体对象属性拷贝工具及其主要特点,有一些可能很多同学在实际开发中都有所涉及,这里再做一下详细的汇总:
-
ModelMapper
-
概述:ModelMapper 是一个简单易用的对象映射工具,它可以自动将一个对象的属性映射到另一个对象中。
-
特点:
-
支持自动映射。
-
提供了配置映射规则的能力。
-
可以处理嵌套对象。
-
支持自定义映射逻辑。
-
-
-
Dozer
-
概述:Dozer 是另一个流行的属性拷贝工具,它旨在简化对象之间的属性映射。
-
特点:
-
支持自动映射。
-
提供了映射配置文件。
-
支持自定义映射逻辑。
-
支持类型转换。
-
-
-
MapStruct
-
概述:MapStruct 是一个代码生成工具,它通过生成专用的映射器类来提高映射效率。
-
特点:
-
自动生成映射代码。
-
支持类型转换。
-
提供了映射配置的灵活性。
-
可以处理复杂对象。
-
-
-
BeanUtils (Apache Commons Lang)
-
概述:Apache Commons Lang 包含了一个名为
BeanUtils
的工具类,用于复制一个对象的属性到另一个对象。 -
特点:
-
简单易用。
-
支持基本类型和包装类型的属性复制。
-
不支持自定义映射逻辑。
-
-
-
Reflections (Java 标准库)
-
概述:Java 标准库提供了反射 API,可以用来实现属性拷贝。
-
特点:
-
完全由 Java 标准库提供。
-
可以实现自定义逻辑。
-
性能较低,因为反射操作通常比直接调用方法慢。
-
-
-
Spring Framework (BeanUtils)
-
概述:Spring Framework 提供了一个
BeanUtils
类,可以用于属性拷贝。 -
特点:
-
简单易用。
-
支持基本类型和包装类型的属性复制。
-
不支持自定义映射逻辑。
-
-
四、MapStruct 介绍
上文详细介绍了常用的实体对象属性拷贝工具,以及各自的特点,可以在自己的项目中酌情选择使用,下面将会详细介绍很多同学没有用过的一种实体对象属性拷贝工具MapStruct 。
4.1 MapStruct 是什么
MapStruct 是一个用于 Java 的代码生成库,它极大地简化了对象到对象映射的过程。MapStruct 通过自动生成映射代码来帮助开发者减少模板代码的编写,并且可以轻松地实现 Java 对象之间的属性映射。
4.2 MapStruct 主要特点和优势
MapStruct 的主要特点包括:
-
代码生成
-
MapStruct 在编译时生成映射器接口的实现类,这些类包含映射方法的具体实现。
-
生成的代码是高效的,因为它们不依赖于反射,而是直接访问字段或调用方法。
-
-
使用简单
-
MapStruct 需要开发者定义一个映射器接口,然后在接口中声明映射方法。
-
开发者不需要自己实现映射逻辑,MapStruct 会自动生成。
-
-
可以灵活定制
-
支持自定义映射器,允许开发者覆盖默认的行为。
-
可以使用注解来控制映射过程中的特殊行为,比如忽略某些字段或使用特定的方法来映射某些字段。
-
-
支持复杂映射:
-
支持列表、集合、数组等集合类型的映射。
-
支持嵌套对象的映射。
-
-
类型安全
-
由于映射是在编译时生成的,因此 MapStruct 提供了类型安全的保障。
-
如果映射配置有误,编译器会在编译阶段报错。
-
-
性能高
-
生成的映射器类执行速度快,因为它们不使用反射,而是直接调用方法或访问字段。
-
-
集成方便
-
MapStruct 可以与 Spring、Micronaut 和其他 Java 框架无缝集成。
-
4.3 MapStruct 使用流程
如何开始使用MapStruct 呢?下面是一个简单的使用步骤:
-
引入依赖包;
-
定义映射器接口:
-
使用
@Mapper
注解定义映射器接口。 -
接口中声明映射方法,例如从一个 DTO 类型映射到另一个 DTO 类型。
-
-
自定义映射:
-
使用
@Mapping
注解来指定特定的映射规则。 -
可以定义
@BeanMapping
来控制整个对象的映射方式。
-
-
配置和使用:
-
将映射器作为 Spring Bean 注入到需要的地方。
-
在非 Spring 环境下也可以通过构造函数创建映射器实例并使用。
-
五、MapStruct 使用案例操作演示
接下来通过代码和示例演示如何使用MapStruct
5.1 导入依赖
在你的maven工程中引入MapStruct 核心依赖
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.3.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.3.Final</version>
</dependency>
5.2 MapStruct 基础案例
下面提供了一个示例案例,描述如何使用MapStruct 将一个对象映射到另一个对象中;
5.2.1 提供一个源对象和目标对象
提供一个原始对象,里面有几个基本的属性,后续需要将这个对象映射到其他对象模型中
import lombok.Data;
@Data
public class UserMetadata {
private Integer userId;
private String userName;
private String password;
private String realName;
private String deptName;
}
映射的对象
import lombok.Data;
import lombok.ToString;
@Data
@ToString
public class UserModel {
private Integer userId;
private String userName;
private String password;
private String realName;
private String deptName;
}
5.2.2 提供mapstruct自定义映射接口
在该接口中,提供具体的映射方法,可以理解为之前使用 bean的拷贝工具类的方法:
核心方法:userCopier
-
通过这个方法,传入源对象UserMetadata,通过mapstruct映射之后返回UserModel这个对象;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface UserCopier {
UserCopier INSTANCE = Mappers.getMapper(UserCopier.class);
UserModel userCopier(UserMetadata userMetadata);
}
5.2.3 测试类
提供如下测试代码
public class MainTest {
public static void main(String[] args) {
UserMetadata userMetadata = new UserMetadata();
userMetadata.setUserId(111);
userMetadata.setUserName("merry");
userMetadata.setPassword("123321");
userMetadata.setRealName("merry");
UserModel userModel = UserCopier.INSTANCE.userCopier(userMetadata);
System.out.println(userModel);
}
}
运行之后可以看到,UserMetadata对象中的属性全部拷贝到UserModel中
5.2 MapStruct 字段映射转换
事实上在实际开发中,并不是两个对象的属性都能完全匹配上,比如A对象有10个属性,B对象也有10个,如果两个对象属性值完全一样,直接使用上述的方式没问题,如果B对象中有两个属性不一样,此时映射之后,B对象的这两个属性就没有被赋值,在之前我们使用Bean拷贝工具类的时候,对于这个问题,通常是下面的做法:
-
第一步先进行Bean对象的拷贝,将能够映射的属性全部拷贝(一般是字段名称完全相同);
-
第二步针对那些属性名称不一样的,手动做一下设置;
public UserVO getByUserId(int userId) {
User user = userMapper.getById(userId);
UserVO userVO = new UserVO();
BeanUtils.copyProperties(user,userVO);
userVO.setDeptName(user.getDeptName());
userVO.setRealName(user.getRealName());
return userVO;
}
但是如果遇到差异化的属性比较多的情况下,上述的方式仍然显得繁琐,下面看看MapStruct 是如何解决这类问题的呢?在下面的两个对象中,目标对象中有两个字段与源对象不同,我们的目标是,将TargetUser对象中的这两个属性能够与SourceUser正常映射;
5.2.1 差异化字段映射操作
源对象
@Data
public class SourceUser {
private Integer userId;
private String userName;
private String password;
private String realName;
private String deptName;
}
目标映射对象
@Data
@ToString
public class TargetUser {
private Integer userId;
private String userName;
private String password;
private String real;
private String dept;
}
映射接口
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
@Mapper
public interface UserCopyMappings {
UserCopyMappings INSTANCE = Mappers.getMapper(UserCopyMappings.class);
@Mapping(source = "realName" ,target = "real")
@Mapping(source = "deptName" ,target = "dept")
TargetUser userCopier(SourceUser sourceUser);
}
在上面的映射接口中,userCopier这个方法上面,为了将差异化的字段进行映射,添加了@Mapping注解这个字段,这个注解中,指定了源对象的字段名称,以及映射到目标对象中的属性字段名称,如果有多个,可以参考上述的做法,继续添加即可
测试类
public static void main(String[] args) {
SourceUser sourceUser = new SourceUser();
sourceUser.setUserId(111);
sourceUser.setUserName("merry");
sourceUser.setPassword("123321");
sourceUser.setRealName("merry");
sourceUser.setDeptName("测试部");
TargetUser targetUser = UserCopyMappings.INSTANCE.userCopier(sourceUser);
System.out.println(targetUser);
}
运行上面的代码,可以看的,目标对象中的差异化字段也能够正常映射
5.2.2 忽略字段映射
有些字段如果不需要映射,也可以在 @Mapping注解中进行添加,如下,我们忽略 userName这个字段的映射,就可以像下面这样配置
5.2.3 给属性赋默认值
映射的对象中,如果某个字段需要默认值,可以@Mapping中进行属性赋值,比如在TargetUser中增加了一个createDate的属性,但是源对象中并没有这个字段,此时可以像下面这样配置,即在expression中进行配置
再次运行代码可以看的时间字段已经被赋值了
5.2.4 引用对象作为内部属性赋值
对象A中引用了另一个对象B作为属性,此时要将包含对象B内部的属性一起映射出去给对象C的情况,如下,在SourceUser中还持有一个ExtendUser对象
ExtendUser对象中有其他的两个属性
@Data
public class ExtendUser {
private String address;
private String phone;
}
TargetUser
@Data
@ToString
public class TargetUser {
private Integer userId;
private String userName;
private String password;
private String real;
private String dept;
private LocalDateTime createDate;
private String address;
private String phone;
}
在这种情况下要通过MapStruct 将SourceUser映射给TargetUser,可以参考下面的写法
测试代码
public static void main(String[] args) {
SourceUser sourceUser = new SourceUser();
sourceUser.setUserId(111);
sourceUser.setUserName("merry");
sourceUser.setPassword("123321");
sourceUser.setRealName("merry");
sourceUser.setDeptName("测试部");
ExtendUser extendUser = new ExtendUser();
extendUser.setAddress("beijing");
extendUser.setPhone("133***");
sourceUser.setExtendUser(extendUser);
TargetUser targetUser = UserCopyMappings.INSTANCE.userCopier(sourceUser);
System.out.println(targetUser);
}
运行上面的代码,可以看到这两个属性也映射过去了
5.2.5 映射对象属性更新
通过A对象的属性值更新映射对象B中的属性值,首先在UserCopyMappings中定义一个更新的方法
-
@MappingTarget 注解后面的对象,表示的就是被覆盖的映射对象
void updateTargetUser(SourceUser sourceUser,@MappingTarget TargetUser targetUser);
测试代码
public static void main(String[] args) {
SourceUser sourceUser = new SourceUser();
sourceUser.setUserId(111);
sourceUser.setUserName("merry");
sourceUser.setPassword("123321");
sourceUser.setRealName("merry");
sourceUser.setDeptName("测试部");
TargetUser targetUser = new TargetUser();
UserCopyMappings.INSTANCE.updateTargetUser(sourceUser,targetUser);
System.out.println(targetUser);
}
运行之后,可以看到SourceUser中的属性值填充到TargetUser对象中了
5.3 MapStruct 数据类型转换
MapStruct 可以对常规的数据类型进行自动转换,这样的话,可以减少开发人员在对象属性定义中指定字段类型的限制。看下面的示例。
5.3.1 int 与string类型的转换
SourceModel源对象有下面几个属性
@Data
public class SourceModel implements Serializable {
private int id;
private int roleId;
private String roleName;
}
TargetModel中也有三个属性,不过roleId是string类型的
@Data
@ToString
public class TargetModel implements Serializable {
private int id;
private String roleId;
private String roleName;
}
提供一个MapStruct 的拷贝方法
@Mapper
public interface ModelCopyMappings {
ModelCopyMappings INSTANCE = Mappers.getMapper(ModelCopyMappings.class);
TargetModel modelCopier(SourceModel sourceModel);
}
测试代码
public static void main(String[] args) {
SourceModel sourceModel = new SourceModel();
sourceModel.setId(1);
sourceModel.setRoleId(111);
sourceModel.setRoleName("管理员");
TargetModel targetModel = ModelCopyMappings.INSTANCE.modelCopier(sourceModel);
System.out.println(targetModel);
}
运行之后,可以看到尽管roleId在targetModel中是int类型,MapStruct 却可以正常的映射过去
5.3.2 string类型与enum类型转换
在SourceModel中提供一个string类型的字段
在TargetModel中提供一个enum类型的字段
测试代码
public static void main(String[] args) {
SourceModel sourceModel = new SourceModel();
sourceModel.setId(1);
sourceModel.setRoleId(111);
sourceModel.setRoleName("管理员");
String name = UserEnum.TYPE_01.getName();
System.out.println(name);
sourceModel.setUserEnum(name);
TargetModel targetModel = ModelCopyMappings.INSTANCE.modelCopier(sourceModel);
System.out.println(targetModel);
}
5.3.3 格式化显示
如果希望映射后的字段以特定的格式要求进行展示,也可以使用上文中讲到的@Mapping注解进行处理,如下:
SourceModel中增加一个price的属性
TargetModel中也有这个属性,不过类型是string
如果需要将字段转换为自定义的格式输出,只需要在转换方法上面添加@Mapping注解,并指定格式化输出即可,如下:
@Mapper
public interface ModelCopyMappings {
ModelCopyMappings INSTANCE = Mappers.getMapper(ModelCopyMappings.class);
@Mapping(source = "price",target = "price",numberFormat = "¥#.00")
TargetModel modelCopier(SourceModel sourceModel);
}
测试代码
public static void main(String[] args) {
SourceModel sourceModel = new SourceModel();
sourceModel.setId(1);
sourceModel.setRoleId(111);
sourceModel.setRoleName("管理员");
sourceModel.setPrice(100);
TargetModel targetModel = ModelCopyMappings.INSTANCE.modelCopier(sourceModel);
System.out.println(targetModel);
}
运行之后看到如下效果
5.3.4 时间类型格式化显示
时间类型的转换也是一种开发中常见的场景,基于上面的对象,在SourceModel中添加一个Date类型的字段
在TargetModel中也有相同名称的字段,类型为string
在转换方法中添加如下的映射属性
@Mapping(source = "createTime",target = "createTime",dateFormat = "yyyy-MM-dd HH:mm:ss")
@Mapping(source = "price",target = "price",numberFormat = "¥#.00")
TargetModel modelCopier(SourceModel sourceModel);
运行测试代码,即可转为期望的格式输出
5.4 MapStruct 集合类型映射
在bean对象中,经常会定义一些集合类型的属性,基于这类需求,MapStruct 也提供了相应的解决办法,下面以案例的方式详细说明。
5.4.1 Map与Bean的映射
需求,将map<key,val>映射为bean,映射后的对象
@Data
public class TargetModel {
private Integer id;
private Integer roleId;
private String roleName;
private String price;
private String createTime;
}
映射方法
@Mapper
public interface BeanCopyMappings {
BeanCopyMappings INSTANCE = Mappers.getMapper(BeanCopyMappings.class);
TargetModel modelCopier(Map<String,String> sourceMap);
}
测试类
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
map.put("id","1");
map.put("roleId","11");
map.put("roleName","管理员");
map.put("price","12");
TargetModel targetModel = BeanCopyMappings.INSTANCE.modelCopier(map);
System.out.println(targetModel);
}
执行结果
5.4.2 集合数据类型的转换
需求,将set<string>类型的集合转为set<Integer>类型,在映射方法中定义
测试代码
public static void main(String[] args) {
Set<String> sourceSet = new HashSet<>();
sourceSet.add("1");
sourceSet.add("12");
sourceSet.add("123");
Set<Integer> integers = BeanCopyMappings.INSTANCE.strToInteger(sourceSet);
System.out.println(integers);
}
执行结果
5.4.3 集合拷贝
集合对象的拷贝在开发中是一个很常见的需求,如果使用MapStruct 来做,也可以实现诸如bean拷贝工具类的效果,定义一个拷贝的方法
List<TargetBean> copyList( List<TargetBean> sourceList);
测试代码
public static void main(String[] args) {
List<TargetBean> list = new ArrayList<>();
TargetBean targetBean = new TargetBean();
targetBean.setId(1);
targetBean.setRoleName("普通用户");
targetBean.setRoleId(111);
targetBean.setPrice(12);
list.add(targetBean);
List<TargetBean> copyList = BeanCopyMappings.INSTANCE.copyList(list);
System.out.println(copyList);
}
注意,这种方式的拷贝属于浅拷贝,即拷贝的是集合的引用
5.5 MapStruct 自定义字段映射
在实际开发中可能会遇到这么一种需求,比如某个字段,数据库查询出来的是int类型的枚举值,比如sex(0:男,1:女),order_status(0:未出库,1:已出库 ...),但是接口返回值中希望看到的是枚举值对应的中文,类似这样的场景很多,在MapStruct 中,可以通过自定义字段的映射来实现,下面看具体的实现过程。
5.5.1 定义源对象和目标对象
SourceOrder
@Data
public class SourceOrder {
private int orderId;
private int orderStatus;
}
TargetOrder
@Data
public class TargetOrder {
private int orderId;
private String orderStatus;
}
5.5.2 定义对应映射方法接口
@Mapper
@DecoratedWith(OrderDecorator.class)
public interface OrderCopyMappings {
OrderCopyMappings INSTANCE = Mappers.getMapper(OrderCopyMappings.class);
TargetOrder orderCopier(SourceOrder sourceOrder);
}
5.5.3 定义转换器的抽象类
通过该抽象类,重写orderCopier的方法,在重写的方法里面对特定的字段进行逻辑改造,如果有更多的类似的需要转换的逻辑均可以在这里定义
import lombok.AllArgsConstructor;
@AllArgsConstructor
public abstract class OrderDecorator implements OrderCopyMappings{
private final OrderCopyMappings orderCopyMappings;
@Override
public TargetOrder orderCopier(SourceOrder sourceOrder) {
TargetOrder targetOrder = orderCopyMappings.orderCopier(sourceOrder);
int orderStatus = sourceOrder.getOrderStatus();
if(0==orderStatus){
targetOrder.setOrderStatus("未出库");
}else if(1==orderStatus){
targetOrder.setOrderStatus("已出库");
} else if(2 == orderStatus){
targetOrder.setOrderStatus("运输中");
}
return targetOrder;
}
}
5.5.4 测试代码
public static void main(String[] args) {
SourceOrder sourceOrder = new SourceOrder();
sourceOrder.setOrderId(1);
sourceOrder.setOrderStatus(1);
TargetOrder targetOrder = OrderCopyMappings.INSTANCE.orderCopier(sourceOrder);
System.out.println(targetOrder);
}
5.5.5 测试效果
执行上面的代码,可以看到如下效果
六、springboot 整合MapStruct
有了上面的学习和了解之后,接下来探讨如何在springboot 中集成并使用MapStruct
6.1 前置准备
-
创建springboot工程(略)
-
导入MapStruct 依赖(上文的依赖)
6.2 整合详细过程
6.2.1 提供源对象和目标对象
源对象
@Data
public class User {
private Integer userId;
private String userName;
private String password;
private String realName;
private String deptName;
}
目标对象
@Data
public class UserVO {
private Integer userId;
private String userName;
private String password;
private String realName;
private String deptName;
}
6.2.2 MapStruct 对象映射接口
提供对象映射接口,在这个接口里面统一定义需要的方法,如果还有更复杂的业务需求,都可以参照上文中的方式在这个里面进行处理;
import com.congge.entity.User;
import com.congge.entity.vo.UserVO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper(componentModel = "spring")
public interface UserMappings {
UserMappings INSTANCE = Mappers.getMapper(UserMappings.class);
UserVO toUserVO(User user);
}
6.2.3 测试接口
提供一个测试接口,在这个接口中,注入上面的UserMappings,调用里面的toUserVO方法,将User映射到UserVO中;
@Autowired
private UserService userService;
@Resource
private UserMappings userMappings;
//localhost:8081/getUserById?userId=1
@GetMapping("/getUserById")
public UserVO getUserById(@RequestParam Integer userId){
User dbUser = userService.getById(userId);
//UserVO userVO = UserMappings.INSTANCE.toUserVO(dbUser);
UserVO userVO = userMappings.toUserVO(dbUser);
return userVO;
}
运行工程之后,调用一下上述接口,可以看到,能够正确的完成从User到UserVO数据的映射
七、写在文末
本文通过较大的篇幅详细介绍了对象映射工具MapStruct 的使用,并通过丰富的案例操作演示了MapStruct 中技术点的使用,对比当下各类开源组件来说,MapStruct 可以说还是有很强的优势的,灵活、高效、可以根据自身的需求自定义等,这些都是其他的对象映射组件不具备的,可以作为技术选型阶段的参考,本篇到此结束,感谢观看。