定義一個dto對象_業務代碼的救星——Java 對象轉換框架 MapStruct 妙用

1374143aa4877b82d86b045be9c080d8.png

在業務項目的開發中,我們經常需要將 Java 對象進行轉換,比如從將外部微服務得到的對象轉換為本域的業務對象 domainobject,將 domainobject 轉為數據持久層的 dataobject,將 domainobject 轉換為 DTO 以便返回給外部調用方等。在轉換時大部分屬性都是相同的,只有少部分的不同,如果手工編寫轉換代碼,會很繁瑣。這時我們可以通過一些對象轉換框架來更方便的做這件事情。

這樣的對象轉換框架有不少,比較有名的有 ModelMapper 和 MapStruct。它們所使用的實現技術不同,ModelMapper 是基于反射的,通過反射來查找實體對象的字段,并讀取或寫入值,這樣的方式實現原理簡單,但性能很差。與 ModelMapper 框架不同的是,MapStruct 是基于編譯階段代碼生成的,生成的轉換代碼在運行的時候跟一般的代碼一樣,沒有額外的性能損失。本文重點介紹 MapStruct。

業務場景

假設現在有這么個場景,從數據庫查詢出來了一個 user 對象(包含 id,用戶名,密碼,手機號,郵箱,角色這些字段)和一個對應的角色對象 role(包含 id,角色名,角色描述這些字段),現在在 controller 需要用到 user 對象的 id,用戶名,和角色對象的角色名三個屬性。一種方式是直接把兩個對象傳遞到 controller 層,但是這樣會多出很多沒用的屬性。更通用的方式是需要用到的屬性封裝成一個類(DTO),通過傳輸這個類的實例來完成數據傳輸。

實現方式之使用傳統方式

如下:

User.java@AllArgsConstructor
@Data
public class User {private Long id;private String username;private String password;private String phoneNum;private String email;private Role role;
}
Role.java@AllArgsConstructor
@Data
public class Role {private Long id;private String roleName;private String description;
}
UserRoleDto.java
@Data
public class UserRoleDto {/*** 用戶id*/private Long userId;/*** 用戶名*/private String name;/*** 角色名*/private String roleName;
}
MainTest.java
測試類,模擬將 user 對象轉換成 UserRoleDto 對象
public class MainTest {User user = null;/*** 模擬從數據庫中查出 user 對象*/@Beforepublic void before() {Role role  = new Role(2L, "administrator", "超級管理員");user  = new User(1L, "zhangsan", "12345", "17677778888", "123@qq.com", role);}/*** 模擬把 user 對象轉換成 UserRoleDto 對象*/@Testpublic void test1() {UserRoleDto userRoleDto = new UserRoleDto();userRoleDto.setUserId(user.getId());userRoleDto.setName(user.getUsername());userRoleDto.setRoleName(user.getRole().getRoleName());System.out.println(userRoleDto);}
}

運行結果

10554362c510acc160227027e8b2f7d2.png

上邊的代碼或許暫時看起來還是比較簡潔的,但是我們需要注意的一點就是平時業務開發中的對象屬性遠不是上述代碼中簡簡單單的幾個字段,有可能會有數十個字段,同理也會數十個對象需要轉換,我們如果還是通過 getter、setter 的方式把一個對象屬性值復制到另一個對象中去還是非常麻煩的,不過不用擔心,今天要介紹給大家的 MapStruct 就是用于解決這種問題的。

實現方式之使用 MapStruct

這里我們沿用上述代碼中的基本對象 User.javaRole.javaUserRoleDto.java。然后新建一個 UserRoleMapper.java,這個來用來定義 User.javaRole.javaUserRoleDto.java之間屬性對應規則。

在這之前我們需要引入 MapStruct 的 pom 引用:

 <dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-jdk8</artifactId><version>1.3.0.Final</version>
</dependency>
UserRoleMapper.java
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;/*** @Mapper 定義這是一個MapStruct對象屬性轉換接口,在這個類里面規定轉換規則*         在項目構建時,會自動生成改接口的實現類,這個實現類將實現對象屬性值復制*/
@Mapper
public interface UserRoleMapper {/*** 獲取該類自動生成的實現類的實例* 接口中的屬性都是 public static final 的* 方法都是public abstract 的*/UserRoleMapper INSTANCES = Mappers.getMapper(UserRoleMapper.class);/*** 這個方法就是用于實現對象屬性復制的方法** @Mapping 用來定義屬性復制規則*              source 指定源對象屬性*              target 指定目標對象屬性** @param user 這個參數就是源對象,也就是需要被復制的對象* @return 返回的是目標對象,就是最終的結果對象*/@Mappings({@Mapping(source = "id", target = "userId"),@Mapping(source = "username", target = "name"),@Mapping(source = "role.roleName", target = "roleName")})UserRoleDto toUserRoleDto(User user);}

測試一下結果

MainTest.java
/*** 模擬通過MapStruct把user對象轉換成UserRoleDto對象*/@Testpublic void test2() {UserRoleDto userRoleDto = UserRoleMapper.INSTANCES.toUserRoleDto(user);System.out.println(userRoleDto);}

呃,很明顯,運行竟然報錯了,具體異常如下:

2a60a3fade0d84ce4f0e8ddfe030ed42.png

核心是這一句 :java.lang.ClassNotFoundException:Cannotfind implementationfortop.zhoudl.mapstruct.UserRoleMapper ,也就是說沒有找到 UserRoleMapper 類的實現類。

通過查閱一些資料可得:

MapStruct 是一個可以處理注解的Java編譯器插件,可以在命令行中使用,也可以在 IDE 中使用。MapStruc t有一些默認配置,但是也為用戶提供了自己進行配置的途徑。缺點就是這玩意在使用工具自帶的編譯器時不會生成實現類,需要通過 maven 的方式來進行編譯,然后才會生成實現類。

所以我們需要增加一個編譯插件到 pom 文件中:

<!-- 引入 processor -->
<dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>1.3.0.Final</version><scope>provided</scope>
</dependency>
<!--為 Maven compile plugin 設置 annotation processor -->
<plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.5.1</version><configuration><source>1.8</source><target>1.8</target><annotationProcessorPaths><path><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>1.2.0.Final</version></path></annotationProcessorPaths></configuration></plugin>
</plugins>

然后我們運行程序就可以得到自己想要的結果了

10554362c510acc160227027e8b2f7d2.png

安裝 MapStruct 插件

使用 MapStruct,還有一個缺點就是,當屬性改名的時候,因為在 Mapper 上注解中配置的名字是在字符串里面,因此不會自動同步的。所以 MapStruct 提供了一個插件來解決這個問題,同時還提供代碼自動提示、點擊跳轉到實現等功能。

關于插件的更多信息,參見 MapStruct support for IntelliJ IDEA

安裝插件的過程

在 IDEA 中依次打開 File - > Settings - > Plugins

然后在 Markeyplace 搜索框中輸入 mapstruct,點擊 install,然后重啟 IDE 即可。

221a0c876820879d4c286220c1fa1c0a.png

一些可能會出現的問題

  • 找不到注釋處理程序:在 pom.xml 中增加 mapstruct-processor 的依賴
  • 沒有找到實現類:在 pom.xml 中加入對 mapstruct-processor 的依賴
  • 在 IDEA 里面 enable Annotation Processor
  • 使用 Lombok 的情況下,編譯時報 Data 類的 setter/getter 找不到:把 lombok 加入到annotationProcessorPath,如下圖

138b04304037c49cc957679be43b1d06.png

總結

MapSturct 是一個生成類型安全, 高性能且無依賴的 JavaBean 映射代碼的注解處理器(annotation processor)。

作為一個注解處理器, 通過 MapStruct 生成的代碼具有怎么樣的優勢呢?抓一下重點:

  1. 注解處理器
  2. 可以生成 JavaBean 之間的映射代碼
  3. 類型安全, 高性能, 無依賴性

高性能

這是相對反射來說的, 反射需要去讀取字節碼的內容, 花銷會比較大。而通過 MapStruct來生成的代碼, 其類似于人手寫,代碼執行速度上可以得到保證。(前面例子中生成的代碼可以在編譯后看到,在項目的 target/generated-sources/annotations 目錄里可以看到具體代碼)。

易于 debug

在我們生成的代碼中, 我們可以輕易的進行 debug。但是如果是使用反射實現代碼的時候, 一旦出現了問題, 很多時候是比較難找到原因。

使用相對簡單

如果是完全映射的, 使用起來肯定沒有反射簡單。用類似 BeanUtils 這些工具一條語句就搞定了。但是,如果需要進行特殊的匹配(特殊類型轉換, 多對一轉換等), MapStruct 的優勢就比較明顯了,基本上我們只需要在使用的時候聲明一個接口, 接口下寫對應的方法, 就可以使用了(當然, 如果有特殊情況, 是需要額處理一下的)。

代碼獨立

生成的代碼是對立的, 沒有運行時的依賴


原作者:zhoudl
原文鏈接:業務代碼的救星——Java 對象轉換框架 MapStruct 妙用
原出處:公眾號

26b63fa09423e8fdc2c26c146af8630d.gif

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/443292.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/443292.shtml
英文地址,請注明出處:http://en.pswp.cn/news/443292.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

JVM調優總結 -Xms -Xmx -Xmn -Xss

堆大小設置 JVM 中最大堆大小有三方面限制&#xff1a;相關操作系統的數據模型&#xff08;32-bt還是64-bit&#xff09;限制&#xff1b;系統的可用虛擬內存限制&#xff1b;系統的可用物理內存限制。32位系統 下&#xff0c;一般限制在1.5G~2G&#xff1b;64為操作系統對內存…

discuz設置用戶每天回帖數_[建站教程]Discuz3.4設置QQ互聯登陸教程

雖然現在很多人已經不在使用QQ了&#xff0c;但瘦死的駱駝比馬大&#xff0c;QQ的用戶基數還是很大&#xff0c;而且QQ里有大量的年輕用戶&#xff0c;像我的表妹&#xff0c;表弟剛上初中。他們是忠誠的QQ用戶。為了獲取這批年輕的用戶&#xff0c;我們還是有必要讓網站支持QQ…

五種線程池的對比與使用

今天對五種常見的java內置線程池進行講解。 線程使用的demo public static void cache() {ExecutorService pool Executors.newCachedThreadPool();long start System.currentTimeMillis();pool.execute(() -> {int sum 0;for (int i 0; i < 10; i) {sum (int) Ma…

16進制加法 keil_C/C++編程筆記:C語言進制詳解,二進制、八進制和十六進制

我們平時使用的數字都是由 0~9 共十個數字組成的&#xff0c;例如 1、9、10、297、952 等&#xff0c;一個數字最多能表示九&#xff0c;如果要表示十、十一、二十九、一百等&#xff0c;就需要多個數字組合起來。例如表示 58 的結果&#xff0c;一個數字不夠&#xff0c;只能”…

MySQL的索引是什么?怎么優化?

索引類似大學圖書館建書目索引&#xff0c;可以提高數據檢索的效率&#xff0c;降低數據庫的IO成本。MySQL在300萬條記錄左右性能開始逐漸下降&#xff0c;雖然官方文檔說500~800w記錄&#xff0c;所以大數據量建立索引是非常有必要的。MySQL提供了Explain&#xff0c;用于顯示…

通達信板塊監控指標_通達信洞察強勢板塊指標公式

N:13;P:4;RN:27;VVAR1:(MA(CLOSE,80)-MA(CLOSE,13)/3);VVAR2:( MA((CLOSE-VVAR1)/VVAR1,1));VVAR3:(CLOSE-LLV(LOW,28))/(HHV(HIGH,28)-LLV(LOW,28))*100;VVAR4:SMA(VVAR3,4,1);MMA:EMA(VVAR2,12)*0.7;MMB:EMA(VVAR2,3);快到底:IF(LLV(MMB-MMA,12)>0,0,-20),LINETHICK2,COLO…

12306能刪候補訂單記錄_12306候補購票功能在哪里怎么用 火車票候補購票使用攻略...

12月27日&#xff0c;12306火車票官方推出了一個「候補購票」功能&#xff0c;目前已經開啟春運試點&#xff0c;對于購買火車票的用戶來說&#xff0c;當沒票可買的時候&#xff0c;可以提交候補購票&#xff0c;又多了一種購票途徑了。不過&#xff0c;很多小伙伴對于候補購票…

GIT提交message規范

<type>(<scope>): <subject> <body> <footer> # type 用于說明 commit 的類別&#xff0c;只允許使用下面7個標識。 feat: 新功能&#xff08;feature&#xff09; fix: 修補bug docs: 文檔&#xff08;documentation&#xff09; style: 格…

git實現審核功能_一文教你如何搭建PDD分傭小程序實現財富自由

隨著拼多多的火爆&#xff0c;很多淘客以各種方式通過推廣拼多多商品獲取返傭來月入萬元&#xff0c;實現財富自由。只要你有流量或者足夠努力&#xff0c;像其他淘客一樣實現睡后過萬財富自由不是夢。本文通過詳細教程教你快速搭建屬于自己的PDD分傭小程序&#xff0c;完成自己…

9型轉x型 cobol_蘭州一餐館推鴛鴦牛肉面 9種面型一面多吃

來源標題&#xff1a;蘭州一餐館推鴛鴦牛肉面&#xff0c;清湯酸菜各一邊還有9種面型&#xff0c;網友&#xff1a;能連吃三碗近日&#xff0c;位于甘肅蘭州的一家牛肉面館推出了鴛鴦牛肉面。一個大碗分隔為兩邊&#xff0c;一邊是傳統清湯牛肉面&#xff0c;另一邊是酸菜牛肉面…

gitignore不起作用

.gitignore中已經標明忽略的文件目錄下的文件&#xff0c;git push的時候還會出現在push的目錄中&#xff0c;原因是因為在git忽略目錄中&#xff0c;新建的文件在git中會有緩存&#xff0c;如果某些文件已經被納入了版本管理中&#xff0c;就算是在.gitignore中已經聲明了忽略…

java填空題 在非靜態成員方法中_成本加成定價法的優點有

【單選題】以下Math類的方法中,-4.4通過哪個方法運算后,結果為-5.0?【填空題】以下程序的輸出結果為?【單選題】下列方法定義中,正確的是()【判斷題】Java 中被 final 關鍵字修飾的變量,不能被重新賦值。【簡答題】請按以下要求編寫程序 (1) 創建一個Rectangle類,添加width和…

【算法系列之十三】二叉樹兩葉節點的最大距離

1、題目描述 給定一棵二叉樹&#xff0c;計算這課二叉樹的直徑長度&#xff0c;即為二叉樹任意兩個節點間的最長路徑。比如&#xff1a; 這棵二叉樹的最長路徑為3。 2、解題思路 使用遞歸進行求解&#xff0c;每次遞歸的過程中&#xff0c;先求出以某個節點為樹根的二…

date比較大小 mybatis_Hibernate 和 MyBatis 哪個更好用?

Java大聯盟幫助萬千Java學習者持續成長關注作者&#xff5c;SylvanasSun鄭沐興https://zhuanlan.zhihu.com/p/21966051B 站搜索&#xff1a;楠哥教你學Java獲取更多優質視頻教程前言由于編程思想與數據庫的設計模式不同&#xff0c;生出了一些ORM框架。核心都是將關系型數據庫和…

簡單的cpu飆升排查方法

1先來一段飆升代碼 public class FindJavaThreadInTaskManager {public static void main(String[] args) {Thread thread new Thread(new Worker());thread.start();}static class Worker implements Runnable {Overridepublic void run() {while (true) {System.out.printl…

tortoisesvn創建部署項目_FrameWork如何進行云托管部署

介紹CloudBase Framework 是云開發官方出品的云原生一體化部署工具&#xff0c;可以幫助開發者將靜態網站、后端服務和小程序等應用&#xff0c;一鍵部署到云開發 Serverless 架構的云平臺上&#xff0c;自動伸縮且無需關心運維&#xff0c;聚焦應用本身&#xff0c;無需關心底…

【算法系列之十四】最大子序和

1、題目描述 給定一個整數數組 nums &#xff0c;找到一個具有最大和的連續子數組&#xff08;子數組最少包含一個元素&#xff09;&#xff0c;返回其最大和。 示例: 輸入: [-2,1,-3,4,-1,2,1,-5,4], 輸出: 6 解釋: 連續子數組 [4,-1,2,1] 的和最大&#xff0c;為 6。 2、…

python的代碼復用技術_Python__函數和代碼復用

主要內容函數的定義和使用實例:七段數碼管的繪制代碼復用與函數遞歸PyInstall庫的使用實例&#xff1a;科赫雪花小包裹函數的定義與使用函數的理解與定義函數的使用及調用過程函數的參數傳遞函數的返回值局部變量和全局變量lambda函數------------------------------------函數…

Queue:poll、offer、element、peek的區別

隊列是一種特殊的線性表&#xff0c;它只允許在表的前端&#xff08;front&#xff09;進行刪除操作&#xff0c;而在表的后端&#xff08;rear&#xff09;進行插入操作。進行插入操作的端稱為隊尾&#xff0c;進行刪除操作的端稱為隊頭。隊列中沒有元素時&#xff0c;稱為空隊…

python實現k均值算法_python實現kMeans算法

聚類是一種無監督的學習&#xff0c;將相似的對象放到同一簇中&#xff0c;有點像是全自動分類&#xff0c;簇內的對象越相似&#xff0c;簇間的對象差別越大&#xff0c;則聚類效果越好。1、k均值聚類算法k均值聚類將數據分為k個簇&#xff0c;每個簇通過其質心&#xff0c;即…