Java MapStruct解了對象映射的毒
前言
MVC模式是目前主流項目的標(biāo)準(zhǔn)開發(fā)模式,這種模式下框架的分層結(jié)構(gòu)清晰,主要分為Controller,Service,Dao。分層的結(jié)構(gòu)下,各層之間的數(shù)據(jù)傳輸要求就會存在差異,我們不能用一個對象來貫穿3層,這樣不符合開發(fā)規(guī)范且不夠靈活。
我們常常會遇到層級之間字段格式需求不一致的情況,例如數(shù)據(jù)庫中某個字段是datetime
日期格式,這個時間戳在數(shù)據(jù)庫中的存儲值為2020-11-06 23:59:59.999999
,但是傳遞給前端的時候要求接口返回yyyy-MM-dd
的格式,或者有些數(shù)據(jù)在數(shù)據(jù)庫中是逗號拼接的String類型,但是前端需要的是切割后的List類型等等。
所以我們提出了層級間的對象模型,就是我們常見的VO,DTO,DO,PO等等。這種區(qū)分層級對象模型的方式雖然清晰化了我們各層級間的對象傳遞,但是對象模型間的相互轉(zhuǎn)換和值拷貝確是讓人感覺很麻煩,拷貝來拷貝去,來來回回,過程重復(fù)乏味,編寫此類映射代碼是一項繁瑣且容易出錯的任務(wù)。
最簡單粗糙的拷貝方法就是不斷的new對象然后對象間的 setter 和 getter,這種方式應(yīng)對字段屬性少的還可以,如果屬性字段很多那么大段的set,get的代碼就顯得很不雅美。因此需要借助對象拷貝工具,目前市場上的也蠻多的像BeanCopy,Dozer等等,但是這些我感覺都不夠好,今天我推薦一個實體映射工具就是 MapStruct。
介紹
MapStruct的官網(wǎng)地址是 https://mapstruct.org/MapStruct,是一個快速安全的bean 映射代碼生成器,只需要通過簡單的注解就可以實現(xiàn)對象間的屬性轉(zhuǎn)換,是一款 Apache LICENSE 2.0 授權(quán)的開源產(chǎn)品,Github的源碼地址是 https://github.com/mapstruct。
通過官網(wǎng)的三連問(What,Why,How)我們可以大概的了解到 MapStruct 的作用,它的優(yōu)勢以及它是如何實現(xiàn)的。
從上面的三連問中我們可以得到如下信息:
- 基于約定優(yōu)于配置的方法 MapStruct 極大地簡化了 Java bean 類型之間的映射的實現(xiàn),通過簡單的注解就可以工作。生成的映射代碼使用普通的方法調(diào)用而不是反射,因此速度快,類型安全且易于理解。
- 在編譯時生成 Bean 映射 與其他映射框架相比,MapStruct 在編譯時生成 Bean 映射,這樣可以確保高性能,而且開發(fā)人員可以快速的得到反饋和徹底的錯誤檢查。
- 一個注釋處理器 MapStruct 是一個注釋處理器,已插入 Java 編譯器,可用于命令行構(gòu)建(Maven,Gradle等),也可用于您首選的IDE中(IDEA,Eclipse等)。
代碼編寫
MapStruct 需要 Java 1.8或更高版本。對于Maven-based 的項目,在pom 文件中添加如下依賴即可
<!-- 指定版本--> <properties> <org.mapstruct.version>1.4.1.Final</org.mapstruct.version> </properties> <!-- 添加依賴 --> <dependencies> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>${org.mapstruct.version}</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>${org.mapstruct.version}</version> </dependency> </dependencies>
基本的依賴引入后就可以編寫代碼了,簡單的定義一個映射類,為了與 Mybatis中的 mapper 接口區(qū)分,我們可以取名為 xxObjectConverter
。
例如汽車對象的映射類名為 CarObjectConverter
,我們有兩個對象模型 DO 和 DTO,它們內(nèi)部的屬性字段如下:
數(shù)據(jù)庫對應(yīng)的持久化對象模型 CarDo
public class Car { @ApiModelProperty(value = "主鍵id") private Long id; @ApiModelProperty(value = "制造商") private String manufacturers; @ApiModelProperty(value = "銷售渠道") private String saleChannel; @ApiModelProperty(value = "生產(chǎn)日期") private Date productionDate; ... }
層級間傳輸?shù)膶ο竽P?CarDto
public class CarDto { @ApiModelProperty(value = "主鍵id") private Long id; @ApiModelProperty(value = "制造商") private String maker; @ApiModelProperty(value = "銷售渠道") private List<Integer> saleChannel; @ApiModelProperty(value = "生產(chǎn)日期") private Date productionDate; ... }
再編寫具體的 MapStruct 對象映射器
@Mapper public interface CarObjectConverter{ CarObjectConverter INSTANCE = Mappers.getMapper(CarObjectConverter.class); @Mapping(target = "maker", source = "manufacturers") CarDto carToCarDto(Car car); }
對于字段名相同的可以不用額外的指定映射規(guī)則,但是字段名不同的屬性則需要指出字段的映射規(guī)則,如上我們持久層 DO 的制造商的字段名是manufacturers
而層級間傳輸?shù)腄TO模型中則是maker
,我們就需要在映射方法上通過@Mapping
注解指出映射規(guī)則,我個人習(xí)慣是喜歡將target
寫在前面,source
寫在后面,這樣是與映射對象的位置保持一致,差異字段多的時候方便對比且不易混淆。
開發(fā)過程中還會經(jīng)常遇到一些日期格式的轉(zhuǎn)換,就如開篇時說的那種,這時我們也可以指定日期的映射規(guī)則
@Mapper public interface CarObjectConverter{ CarObjectConverter INSTANCE = Mappers.getMapper(CarObjectConverter.class); @Mapping(target = "maker", source = "manufacturers") @Mapping(target = "productionDate", dateFormat = "yyyy-MM-dd", source = "productionDate") CarDto carToCarDto(Car car); }
這些都還是一些簡單的字段的映射,但有時候我們兩個對象模型間的字段類型不一致,如上汽車的銷售渠道字段saleChannel
,這個在數(shù)據(jù)庫中是字符串逗號拼接的值1,2,3
,而我們傳遞出去的需要是 List 的 Integer 類型,這種復(fù)雜的如何映射呢?
也是有方法的,我們先編寫一個將字符串逗號分隔然后轉(zhuǎn)成 List 的工具方法,如下
public class CollectionUtils { public static List<Integer> list2String(String str) { if (StringUtils.isNoneBlank(str)) { return Arrays.asList(str.split(",")).stream().map(s -> Integer.valueOf(s.trim())).collect(Collectors.toList()); } return null; } }
然后在映射Mapping中使用表達(dá)式即可
@Mapper public interface CarObjectConverter { CarObjectConverter INSTANCE = Mappers.getMapper(CarObjectConverter.class); @Mapping(target = "maker", source = "manufacturers") @Mapping(target = "productionDate", dateFormat = "yyyy-MM-dd", source = "productionDate") @Mapping(target = "saleChannel", expression = "java(com.jiajian.demo.utils.CollectionUtils.list2String(car.getSaleChannel()))") CarDto carToCarDto(Car car); }
這樣就完成了所有字段的映射工作,我們在需要對象模型轉(zhuǎn)換的地方按照如下方式調(diào)用即可
CarDto carDto = CarObjectConverter.INSTANCE.carToCarDto(car);
這種是單體對象之間的 Copy 很多時候我們需要 List 對象模型間的轉(zhuǎn)換,只需要再寫一個方法carToCarDtos
即可
@Mapper public interface CarObjectConverter{ CarObjectConverter INSTANCE = Mappers.getMapper(CarObjectConverter.class); @Mapping(target = "maker", source = "manufacturers") @Mapping(target = "productionDate", dateFormat = "yyyy-MM-dd", source = "productionDate") @Mapping(target ="saleChannel", expression = "java(com.jiajian.demo.utils.CollectionUtils.list2String(car.getSaleChannel()))") CarDto carToCarDto(Car car); List<CarDto> carToCarDtos(List<Car> carList); }
探個究竟
會不會好奇這是怎么實現(xiàn)的,我們只是創(chuàng)建了一個接口然后在接口方法上加一個注解并在注解里面指定字段的映射規(guī)則就可以實現(xiàn)對象屬性間的拷貝,這是怎么做到的呢?
我們這里通過 MapStruct 創(chuàng)建的只是一個接口,要實現(xiàn)具體的功能接口必有實現(xiàn)。
MapStruct 會在我們代碼編譯的時候為我們創(chuàng)建一個實現(xiàn)類,而這個實現(xiàn)類里面通過字段的setter, getter方法來實現(xiàn)字段的賦值,從而實現(xiàn)對象的映射。
這里需要注意一點:如果你修改了任一映射對象,記得需要先執(zhí)行mvn clean再啟動項目,否則調(diào)試的時候會報錯。
結(jié)尾
MapStrut 的功能遠(yuǎn)不至于上面介紹的這些,我只是挑出幾個常用的語法進(jìn)行示例講解,如果讀者感興趣想深入的了解更多可以參考官方的參考文檔
遇見 MapStruct 后我就開始在項目中拋棄掉了原來的那些 BeanCopyUtils 的工具,相對而言 MapStruct 確實更簡潔且易使用而且定制功能也很強。
從編譯文件可以看出 MapStruct 是通過setter,getter來實現(xiàn)屬性值的拷貝,然后這種方式不是最簡單又最安全高效的嗎?只是 MapStruct 更好的幫助我們實現(xiàn)了,避免了項目中冗余的重復(fù)代碼,大道至簡。
以上就是MapStruct解了對象映射的毒的詳細(xì)內(nèi)容,更多關(guān)于MapStruct的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring-Security對HTTP相應(yīng)頭的安全支持方式
這篇文章主要介紹了Spring-Security對HTTP相應(yīng)頭的安全支持方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-10-10Java16 JDK安裝并設(shè)置環(huán)境變量的方法步驟
突然想起自己大學(xué)剛接觸java的時候,要下載JDK和配置環(huán)境變量,那時候我上網(wǎng)找了很多教學(xué),本文就詳細(xì)的介紹一下Java16 JDK安裝并設(shè)置環(huán)境變量,感興趣的可以了解一下2021-09-09Java實現(xiàn)簡單的飛機大戰(zhàn)游戲(控制主飛機篇)
這篇文章主要為大家詳細(xì)介紹了Java實現(xiàn)簡單的飛機大戰(zhàn)游戲,控制主飛機,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-05-05springboot @RequestBody 接收字符串實例
這篇文章主要介紹了springboot @RequestBody 接收字符串實例,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10SpringBoot使用YML文件進(jìn)行多環(huán)境配置的三種方法
SpringBoot通過其靈活的配置機制,使得在不同環(huán)境中管理應(yīng)用設(shè)置變得簡單,尤其是使用YAML文件進(jìn)行配置,它提供了一種簡潔、易讀的方式來定義應(yīng)用的配置,本文將探討在SpringBoot中使用YAML文件進(jìn)行多環(huán)境配置的三種方法,需要的朋友可以參考下2024-04-04關(guān)于JDK15的新特性之TextBlocks文本塊的引入和使用
這篇文章主要介紹了關(guān)于JDK15的新特性之文本塊的引入和使用,如果具有一種語言學(xué)機制,可以比多行文字更直觀地表示字符串,而且可以跨越多行,而且不會出現(xiàn)轉(zhuǎn)義的視覺混亂,那么這將提高廣泛Java類程序的可讀性和可寫性,需要的朋友可以參考下2023-07-07Java超詳細(xì)教你寫一個學(xué)籍管理系統(tǒng)案例
這篇文章主要介紹了怎么用Java來寫一個學(xué)籍管理系統(tǒng),學(xué)籍管理主要涉及到學(xué)生信息的增刪查改,本篇將詳細(xì)的實現(xiàn),感興趣的朋友跟隨文章往下看看吧2022-03-03Java使用XML與注解方式實現(xiàn)CRUD操作代碼
MyBatis提供了靈活的配置和使用方式,使得數(shù)據(jù)庫操作更加簡潔和高效,通過本文,我們介紹了如何使用MyBatis框架,通過XML映射文件和注解兩種方式來實現(xiàn)數(shù)據(jù)庫的增刪改查操作,感興趣的朋友跟隨小編一起看看吧2024-02-02