环境:SpringBoot3.2.5
1. 简介
在项目开发中,实体(Entity)到DTO(Data Transfer Object,数据传输对象)的转换是一个常见的需求,特别是在前后端分离或微服务架构中。实体通常代表数据库中的表结构,包含业务数据和状态,而DTO则是一种用于封装和传输数据的轻量级对象,主要用于在不同层或服务之间传递数据。
实体到DTO的转换过程旨在将复杂的实体对象转换为更简洁、更适合传输的DTO对象,从而隐藏业务逻辑细节,提高数据传输效率和安全性。
有效的实体到DTO转换策略能够减少数据传输量,提升系统性能,并增强系统的可维护性和可扩展性。
- Spring BeanUtils
- Apache BeanUtils
- Orika
- Cglib BeanCopier
- ModelMapper
- MapStruct
- Getter/Setter
以上对象的转换,你用过哪几种?
1.1 环境准备
接下来,我们先准备2个类,Entity与DTO类
// Entity类
public class User {
private Long id;
private String code;
private String email;
private String name;
private String qq;
private String table;
private String address;
// getter / setter
}
// DTO类
public class UserDTO {
private Long id;
private String code;
private String email;
private String name;
private String qq;
private String table;
private String address;
// getter / setter
}
实体类与DTO类完全一样。
我们下面的测试都会基于下面的数据进行测试:
User user = new User() ;
user.setId(1L) ;
user.setCode("S0001") ;
user.setEmail("pack@qq.com") ;
user.setName("N") ;
user.setQq("6666666") ;
user.setTable("CTO") ;
user.setAddress("XJ") ;
接下来,我们将详细的介绍每一种转换工具的基本使用及其性能情况。
2. 性能对比
2.1 Spring BeanUtils
该工具类是Spring提供位于spring-beans.jar包中。如下是基本使用:
UserDTO target = new UserDTO() ;
BeanUtils.copyProperties(target, user) ;
接下来我们进行10000次的循环测试
long start = System.currentTimeMillis() ;
for (int i = 0; i < 10000; i++) {
UserDTO target = new UserDTO() ;
org.springframework.beans.BeanUtils.copyProperties(target1, user) ;
}
System.out.println("Spring BeanUtils: " + (System.currentTimeMillis() - start) + "ms") ;
输出结果
Spring BeanUtils: 55ms
平均在55ms完成。
注意:首次进行copyProperties时会慢,因为它内部会通过内省获取属性信息然后缓存,之后直接从缓存中获取,后续速度会非常快。
2.2 Apache BeanUtils
我们需要引入以下包
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.4</version>
</dependency>
基本使用示例:
UserDTO target = new UserDTO() ;
BeanUtils.copyProperties(target, user) ;
与Spring的基本一样。进行10000次的循环测试
long start = System.currentTimeMillis() ;
for (int i = 0; i < 10000; i++) {
UserDTO target = new UserDTO() ;
BeanUtils.copyProperties(target, user) ;
}
System.out.println("Apache BeanUtils: " + (System.currentTimeMillis() - start) + "ms") ;
输出结果
Apache BeanUtils: 113ms
该BeanUtils内部也是通过内省+Cache的方式进行处理,但是它的效率相比Spring BeanUtils慢了至少1倍。
2.3 Orika
Orika 能够递归地(以及其他功能)将一个对象的数据复制到另一个对象中。在开发多层应用程序时,它非常有用。
引入以下依赖
<dependency>
<groupId>ma.glasnost.orika</groupId>
<artifactId>orika-core</artifactId>
<version>1.5.4</version>
</dependency>
基本使用示例:
MapperFactory mapperFactory = new DefaultMapperFactory.Builder()
.build() ;
MapperFacade mapper = mapperFactory.getMapperFacade();
UserDTO dto = mapper.map(user, UserDTO.class) ;
如果需要我们还可以注册类型字段的映射关系,如下:
mapperFactory.classMap(User.class, UserDTO.class)
.field("id", "id")
.field("code", "code")
.field("email", "email")
.field("name", "name")
.field("qq", "qq")
.field("table", "table")
.field("address", "address")
.byDefault()
.register() ;
类之间的字段如何进行映射。
接下来,进行10000次的循环测试
long start = System.currentTimeMillis() ;
for (int i = 0; i < 10000; i++) {
mapper.map(user, UserDTO.class) ;
}
System.out.println("orika: " + (System.currentTimeMillis() - start) + "ms") ;
输出结果
orika: 24ms
目前来看性能相当不错。
2.4 Cglib BeanCopier
因为我当前是基于Spring Boot环境,而该BeanCopier类位于spring-core.jar包中。
基本使用示例:
UserDTO target = new UserDTO() ;
BeanCopier.create(User.class, UserDTO.class, false)
.copy(user, target, null) ;
与上面的BeanUtils差不多一行代码搞定。进行10000次的循环测试
long start = System.currentTimeMillis() ;
for (int i = 0; i < 10000; i++) {
UserDTO target = new UserDTO() ;
BeanCopier.create(User.class, UserDTO.class, false)
.copy(user, target, null) ;
}
System.out.println("Cglib BeanCopier: " + (System.currentTimeMillis() - start) + "ms") ;
输出结果
Cglib BeanCopier: 17ms
性能更高了。
2.5 ModelMapper
该开源组件,在之前的文章中已经介绍过了,我们这里就不在啰嗦了,我们直接进行10000次测试。
ModelMapper mapper = new ModelMapper() ;
long start = System.currentTimeMillis() ;
for (int i = 0; i < 10000; i++) {
mapper.map(user, UserDTO.class) ;
}
System.out.println("modelmapper: " + (System.currentTimeMillis() - start) + "ms") ;
输出结果
modelmapper: 145ms
似乎不是很理想啊。
2.6 MapStruct
MapStruct 是一个代码生成器,它基于约定优于配置的方法,极大地简化了 Java Bean 类型之间映射的实现。生成的映射代码使用普通的方法调用,因此速度快、类型安全且易于理解。
MapStruct在编译时生成bean映射,这确保了高性能。它是一个注解处理器,它被插入到 Java 编译器中,既可以在命令行构建(如 Maven、Gradle 等)中使用。
首先,引入依赖。
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.6.3</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.6.3</version>
</dependency>
我们还需要配置编译插件。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.6.3</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
基本使用示例:
定义映射接口
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class) ;
UserDTO userToUserDTO(User user) ;
}
使用
UserMapper.INSTANCE.userToUserDTO(user) ;
上面的插件会根据这里的映射接口Mapper,自动生成接口实现类
图片
而我们不需要直接去使用该实现类,这是由mapstruct来调用。
进行10000的测试。
long start = System.currentTimeMillis() ;
for (int i = 0; i < 10000; i++) {
UserMapper.INSTANCE.userToUserDTO(user) ;
}
System.out.println("mapstruct: " + (System.currentTimeMillis() - start) + "ms") ;
输出结果
mapstruct: 25ms
看起来也还是非常优秀的。
2.7 Getter/Setter
此种方式就是我们自己在代码中进行getter/setter操作。所以,这里我们直接进行10000次的测试
long start = System.currentTimeMillis() ;
for (int i = 0; i < 10000; i++) {
UserDTO target = new UserDTO() ;
target.setId(user.getId()) ;
target.setAddress(user.getAddress()) ;
target.setCode(user.getCode()) ;
target.setEmail(user.getEmail()) ;
target.setName(user.getName()) ;
target.setQq(user.getQq()) ;
target.setTable(user.getTable()) ;
}
System.out.println("Getter Setter: " + (System.currentTimeMillis() - start) + "ms") ;
输出结果
Getter Setter: 2ms
虽然要写更多的代码了,但是性能确是最高的。
2.8 最后总结
图片
要论工具的强大,MapStruct 和 ModelMapper 无疑是佼佼者,但相对而言,MapStruct 的配置和使用略显复杂,ModelMapper相对简单多了,不需要额外的插件支持。如果你对性能的要求不是极高,选择 MapStruct 依然是一个明智且不错的选择。然而,如果你追求极致的性能优化,那么手动编写 getter/setter 代码可能会是更优的方案。