实体到 DTO 转换!这七种方式性能差距太大,最后一个才是王者

开发 前端
实体到DTO的转换过程旨在将复杂的实体对象转换为更简洁、更适合传输的DTO对象,从而隐藏业务逻辑细节,提高数据传输效率和安全性。

环境: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 代码可能会是更优的方案。

责任编辑:武晓燕 来源: Spring全家桶实战案例源码
相关推荐

2017-06-14 16:44:15

JavaScript原型模式对象

2018-06-10 16:31:12

2022-07-01 08:00:44

异步编程FutureTask

2018-03-13 09:00:01

IT架构

2018-03-22 04:48:06

2023-09-26 08:20:12

消息队列RabbitMQ

2022-12-23 10:55:09

CIO方式团队

2022-03-18 14:33:22

限流算法微服务

2024-12-20 12:10:19

2020-12-01 00:15:04

勒索软件漏洞网络攻击

2023-09-07 10:39:25

AI供应链

2023-01-03 13:43:55

团队首席信息官

2023-09-11 14:26:44

智能技术人工智能

2023-07-06 10:36:51

人工智能

2021-07-23 17:15:12

物联网IOT

2020-01-16 12:20:03

人工智能AI税收

2021-07-19 05:58:27

漏洞网络安全网络攻击

2022-09-29 10:32:44

find命令运维

2010-10-15 10:02:01

Mysql表类型

2013-02-22 18:37:50

容错服务器
点赞
收藏

51CTO技术栈公众号