實(shí)體到 DTO 轉(zhuǎn)換!這七種方式性能差距太大,最后一個(gè)才是王者
環(huán)境:SpringBoot3.2.5
1. 簡介
在項(xiàng)目開發(fā)中,實(shí)體(Entity)到DTO(Data Transfer Object,數(shù)據(jù)傳輸對象)的轉(zhuǎn)換是一個(gè)常見的需求,特別是在前后端分離或微服務(wù)架構(gòu)中。實(shí)體通常代表數(shù)據(jù)庫中的表結(jié)構(gòu),包含業(yè)務(wù)數(shù)據(jù)和狀態(tài),而DTO則是一種用于封裝和傳輸數(shù)據(jù)的輕量級對象,主要用于在不同層或服務(wù)之間傳遞數(shù)據(jù)。
實(shí)體到DTO的轉(zhuǎn)換過程旨在將復(fù)雜的實(shí)體對象轉(zhuǎn)換為更簡潔、更適合傳輸?shù)腄TO對象,從而隱藏業(yè)務(wù)邏輯細(xì)節(jié),提高數(shù)據(jù)傳輸效率和安全性。
有效的實(shí)體到DTO轉(zhuǎn)換策略能夠減少數(shù)據(jù)傳輸量,提升系統(tǒng)性能,并增強(qiáng)系統(tǒng)的可維護(hù)性和可擴(kuò)展性。
- Spring BeanUtils
- Apache BeanUtils
- Orika
- Cglib BeanCopier
- ModelMapper
- MapStruct
- Getter/Setter
以上對象的轉(zhuǎn)換,你用過哪幾種?
1.1 環(huán)境準(zhǔn)備
接下來,我們先準(zhǔn)備2個(gè)類,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
}
實(shí)體類與DTO類完全一樣。
我們下面的測試都會基于下面的數(shù)據(jù)進(jìn)行測試:
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") ;
接下來,我們將詳細(xì)的介紹每一種轉(zhuǎn)換工具的基本使用及其性能情況。
2. 性能對比
2.1 Spring BeanUtils
該工具類是Spring提供位于spring-beans.jar包中。如下是基本使用:
UserDTO target = new UserDTO() ;
BeanUtils.copyProperties(target, user) ;
接下來我們進(jìn)行10000次的循環(huán)測試
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") ;
輸出結(jié)果
Spring BeanUtils: 55ms
平均在55ms完成。
注意:首次進(jìn)行copyProperties時(shí)會慢,因?yàn)樗鼉?nèi)部會通過內(nèi)省獲取屬性信息然后緩存,之后直接從緩存中獲取,后續(xù)速度會非常快。
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的基本一樣。進(jìn)行10000次的循環(huán)測試
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") ;
輸出結(jié)果
Apache BeanUtils: 113ms
該BeanUtils內(nèi)部也是通過內(nèi)省+Cache的方式進(jìn)行處理,但是它的效率相比Spring BeanUtils慢了至少1倍。
2.3 Orika
Orika 能夠遞歸地(以及其他功能)將一個(gè)對象的數(shù)據(jù)復(fù)制到另一個(gè)對象中。在開發(fā)多層應(yīng)用程序時(shí),它非常有用。
引入以下依賴
<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) ;
如果需要我們還可以注冊類型字段的映射關(guān)系,如下:
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() ;
類之間的字段如何進(jìn)行映射。
接下來,進(jìn)行10000次的循環(huán)測試
long start = System.currentTimeMillis() ;
for (int i = 0; i < 10000; i++) {
mapper.map(user, UserDTO.class) ;
}
System.out.println("orika: " + (System.currentTimeMillis() - start) + "ms") ;
輸出結(jié)果
orika: 24ms
目前來看性能相當(dāng)不錯(cuò)。
2.4 Cglib BeanCopier
因?yàn)槲耶?dāng)前是基于Spring Boot環(huán)境,而該BeanCopier類位于spring-core.jar包中。
基本使用示例:
UserDTO target = new UserDTO() ;
BeanCopier.create(User.class, UserDTO.class, false)
.copy(user, target, null) ;
與上面的BeanUtils差不多一行代碼搞定。進(jìn)行10000次的循環(huán)測試
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") ;
輸出結(jié)果
Cglib BeanCopier: 17ms
性能更高了。
2.5 ModelMapper
該開源組件,在之前的文章中已經(jīng)介紹過了,我們這里就不在啰嗦了,我們直接進(jìn)行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") ;
輸出結(jié)果
modelmapper: 145ms
似乎不是很理想啊。
2.6 MapStruct
MapStruct 是一個(gè)代碼生成器,它基于約定優(yōu)于配置的方法,極大地簡化了 Java Bean 類型之間映射的實(shí)現(xiàn)。生成的映射代碼使用普通的方法調(diào)用,因此速度快、類型安全且易于理解。
MapStruct在編譯時(shí)生成bean映射,這確保了高性能。它是一個(gè)注解處理器,它被插入到 Java 編譯器中,既可以在命令行構(gòu)建(如 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) ;
上面的插件會根據(jù)這里的映射接口Mapper,自動生成接口實(shí)現(xiàn)類
圖片
而我們不需要直接去使用該實(shí)現(xiàn)類,這是由mapstruct來調(diào)用。
進(jìn)行10000的測試。
long start = System.currentTimeMillis() ;
for (int i = 0; i < 10000; i++) {
UserMapper.INSTANCE.userToUserDTO(user) ;
}
System.out.println("mapstruct: " + (System.currentTimeMillis() - start) + "ms") ;
輸出結(jié)果
mapstruct: 25ms
看起來也還是非常優(yōu)秀的。
2.7 Getter/Setter
此種方式就是我們自己在代碼中進(jìn)行g(shù)etter/setter操作。所以,這里我們直接進(jìn)行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") ;
輸出結(jié)果
Getter Setter: 2ms
雖然要寫更多的代碼了,但是性能確是最高的。
2.8 最后總結(jié)
圖片
要論工具的強(qiáng)大,MapStruct 和 ModelMapper 無疑是佼佼者,但相對而言,MapStruct 的配置和使用略顯復(fù)雜,ModelMapper相對簡單多了,不需要額外的插件支持。如果你對性能的要求不是極高,選擇 MapStruct 依然是一個(gè)明智且不錯(cuò)的選擇。然而,如果你追求極致的性能優(yōu)化,那么手動編寫 getter/setter 代碼可能會是更優(yōu)的方案。