詳解Java中的mapstruct插件使用
實體類的屬性映射怎么可以少了它?
我們都知道,隨著一個工程的越來越成熟,模塊劃分會越來越細(xì),其中實體類一般存于 domain 之中,但 domain 工程最好不要被其他工程依賴,所以其他工程想獲取實體類數(shù)據(jù)時就需要在各自工程寫 model,自定義 model 可以根據(jù)自身業(yè)務(wù)需要映射相應(yīng)的實體屬性。這樣一來,這個映射工程貌似并不簡單了。阿森差點就犯難了……
序
所以阿淼今天就要給大家安利一款叫 mapstruct
的插件,它就是專門用來處理 domin 實體類與 model 類的屬性映射的,我們只需定義 mapper 接口,mapstruct 在編譯的時候就會自動的幫我們實現(xiàn)這個映射接口,避免了麻煩復(fù)雜的映射實現(xiàn)。
那可能有的小伙伴就要問了?為啥不用 BeanUtils
的 copyProperties
方法呢?不也照樣可以實現(xiàn)屬性的映射么?
這個啊,阿淼我開始也是好奇,所以就和 BeanUtils
深入交流了一番,最后才發(fā)現(xiàn),BeanUtils
就是一個大老粗,只能同屬性映射,或者在屬性相同的情況下,允許被映射的對象屬性少;但當(dāng)遇到被映射的屬性數(shù)據(jù)類型被修改或者被映射的字段名被修改,則會導(dǎo)致映射失敗。而 mapstruct
就是一個巧媳婦兒了,她心思細(xì)膩,把我們可能會遇到的情況都給考慮到了(要是阿淼我也能找一個這樣的媳婦兒該多好,內(nèi)心笑出了豬聲)
如下是這個插件的開源項目地址和各種例子:
- Github地址:https://github.com/mapstruct/mapstruct/
- 使用例子:https://github.com/mapstruct/mapstruct-examples
一、準(zhǔn)備工作
接下來,阿淼將和大家一起去解開這個巧媳婦兒的真正面紗,所以我們還需要做一點準(zhǔn)備工作。
1.1、了解@Mapper 注解
從 mybatis3.4.0 開始加入的 @Mapper 注解,目的就是為了不再寫mapper映射文件。
我們只需要在 dao 層定義的接口上使用注解就可以實現(xiàn)sql語句的編寫,例如:
@Select("select * from user where name = #{name}") public User find(String name);
如上就是一個簡單的使用,雖然簡單,但也確實體現(xiàn)出了這個注解的優(yōu)越性,至少少寫了一個xml文件。
但阿淼我今天可不是想跟你探討 @Mapper
注解,我主要是想去看我的巧媳婦兒 mapstruct
,所以我就只是想說下 @Mapper
注解的 componentModel
屬性,componentModel
屬性用于指定自動生成的接口實現(xiàn)類的組件類型,這個屬性支持四個值:
- default: 這是默認(rèn)的情況,mapstruct 不使用任何組件類型, 可以通過Mappers.getMapper(Class)方式獲取自動生成的實例對象。
- cdi: the generated mapper is an application-scoped CDI bean and can be retrieved via @Inject
- spring: 生成的實現(xiàn)類上面會自動添加一個@Component注解,可以通過Spring的 @Autowired方式進行注入
- jsr330: 生成的實現(xiàn)類上會添加@javax.inject.Named 和@Singleton注解,可以通過 @Inject注解獲取
1.2、依賴包
首先需要把依賴包導(dǎo)入,主要由兩個包組成:
org.mapstruct:mapstruct
:包含了一些必要的注解,例如@Mapping。r若我們使用的JDK版本高于1.8,當(dāng)我們在pom里面導(dǎo)入依賴時候,建議使用坐標(biāo)是:org.mapstruct:mapstruct-jdk8
,這可以幫助我們利用一些Java8的新特性。org.mapstruct:mapstruct-processor
:注解處理器,根據(jù)注解自動生成mapper的實現(xiàn)。
<dependency> <groupId>org.mapstruct</groupId> <!-- jdk8以下就使用mapstruct --> <artifactId>mapstruct-jdk8</artifactId> <version>1.2.0.Final</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.2.0.Final</version> </dependency>
好了,準(zhǔn)備工作做完了,接下來我們就看看巧媳婦兒巧在什么地方吧。
二、先簡單玩一把
2.1、定義實體類以及被映射類
// 實體類 @Data @NoArgsConstructor @AllArgsConstructor @Builder public class User { private Integer id; private String name; private String createTime; private LocalDateTime updateTime; } // 被映射類VO1:和實體類一模一樣 @Data @NoArgsConstructor @AllArgsConstructor @Builder public class UserVO1 { private Integer id; private String name; private String createTime; private LocalDateTime updateTime; } // 被映射類VO1:比實體類少一個字段 @Data @NoArgsConstructor @AllArgsConstructor @Builder public class UserVO2 { private Integer id; private String name; private String createTime; }
2.2、定義接口:
當(dāng)實體類和被映射對象屬性相同或者被映射對象屬性值少幾個時:
@Mapper(componentModel = "spring") public interface UserCovertBasic { UserCovertBasic INSTANCE = Mappers.getMapper(UserCovertBasic.class); /** * 字段數(shù)量類型數(shù)量相同,利用工具BeanUtils也可以實現(xiàn)類似效果 * @param source * @return */ UserVO1 toConvertVO1(User source); User fromConvertEntity1(UserVO1 userVO1); /** * 字段數(shù)量類型相同,數(shù)量少:僅能讓多的轉(zhuǎn)換成少的,故沒有fromConvertEntity2 * @param source * @return */ UserVO2 toConvertVO2(User source); }
從上面的代碼可以看出:接口中聲明了一個成員變量INSTANCE,母的是讓客戶端可以訪問 Mapper 接口的實現(xiàn)。
2.3、使用
@RestController public class TestController { @GetMapping("convert") public Object convertEntity() { User user = User.builder() .id(1) .name("張三") .createTime("2020-04-01 11:05:07") .updateTime(LocalDateTime.now()) .build(); List<Object> objectList = new ArrayList<>(); objectList.add(user); // 使用mapstruct UserVO1 userVO1 = UserCovertBasic.INSTANCE.toConvertVO1(user); objectList.add("userVO1:" + UserCovertBasic.INSTANCE.toConvertVO1(user)); objectList.add("userVO1轉(zhuǎn)換回實體類user:" + UserCovertBasic.INSTANCE.fromConvertEntity1(userVO1)); // 輸出轉(zhuǎn)換結(jié)果 objectList.add("userVO2:" + " | " + UserCovertBasic.INSTANCE.toConvertVO2(user)); // 使用BeanUtils UserVO2 userVO22 = new UserVO2(); BeanUtils.copyProperties(user, userVO22); objectList.add("userVO22:" + " | " + userVO22); return objectList; } }
2.4、查看編譯結(jié)果
通過IDE的反編譯功能查看編譯后自動生成 UserCovertBasic
的實現(xiàn)類 UserCovertBasicImpl
,內(nèi)容如下:
@Component public class UserCovertBasicImpl implements UserCovertBasic { public UserCovertBasicImpl() { } public UserVO1 toConvertVO1(User source) { if (source == null) { return null; } else { UserVO1 userVO1 = new UserVO1(); userVO1.setId(source.getId()); userVO1.setName(source.getName()); userVO1.setCreateTime(source.getCreateTime()); userVO1.setUpdateTime(source.getUpdateTime()); return userVO1; } } public User fromConvertEntity1(UserVO1 userVO1) { if (userVO1 == null) { return null; } else { User user = new User(); user.setId(userVO1.getId()); user.setName(userVO1.getName()); user.setCreateTime(userVO1.getCreateTime()); user.setUpdateTime(userVO1.getUpdateTime()); return user; } } public UserVO2 toConvertVO2(User source) { if (source == null) { return null; } else { UserVO2 userVO2 = new UserVO2(); userVO2.setId(source.getId()); userVO2.setName(source.getName()); userVO2.setCreateTime(source.getCreateTime()); return userVO2; } } }
2.5、瀏覽器查看結(jié)果
好了,一個流程就走完了,是不是感覺賊簡單呢?
而且呀,阿淼溫馨提醒:
如果是要轉(zhuǎn)換一個集合的話,只需要把這里的實體類換成集合就行了,例如:
List<UserVO1> toConvertVOList(List<User> source);
三、不簡單的情況
上面已經(jīng)把整個流程都給過了一遍了,相信大家對 mapstruct
也有了一個基礎(chǔ)的了解了,所以接下來的情況我們就不展示全部代碼了,畢竟篇幅也有限,所以就直接上關(guān)鍵代碼(因為不關(guān)鍵的和上面內(nèi)容一樣,哈哈)
3.1、類型不一致
實體類我們還是沿用 User
;被映射對象 UserVO3
改為:
@Data @NoArgsConstructor @AllArgsConstructor @Builder public class UserVO3 { private String id; private String name; // 實體類該屬性是String private LocalDateTime createTime; // 實體類該屬性是LocalDateTime private String updateTime; }
那么我們定義的接口就要稍稍修改一下了:
@Mappings({ @Mapping(target = "createTime", expression = "java(com.java.mmzsblog.util.DateTransform.strToDate(source.getCreateTime()))"), }) UserVO3 toConvertVO3(User source); User fromConvertEntity3(UserVO3 userVO3);
上面 expression
指定的表達(dá)式內(nèi)容如下:
public class DateTransform { public static LocalDateTime strToDate(String str){ DateTimeFormatter df = DateTimeFormatter.ofPattern("yyy-MM-dd HH:mm:ss"); return LocalDateTime.parse("2018-01-12 17:07:05",df); } }
通過IDE的反編譯功能查看編譯后的實現(xiàn)類,結(jié)果是這樣子的:
從圖中我們可以看到,編譯時使用了expression中定義的表達(dá)式對目標(biāo)字段 createTime
進行了轉(zhuǎn)換;然后你還會發(fā)現(xiàn) updateTime
字段也被自動從 LocalDateTime 類型轉(zhuǎn)換成了 String 類型。
阿淼小結(jié):
當(dāng)字段類型不一致時,以下的類型之間是 mapstruct
自動進行類型轉(zhuǎn)換的:
- 1、基本類型及其他們對應(yīng)的包裝類型。
- 此時
mapstruct
會自動進行拆裝箱。不需要人為的處理 - 2、基本類型的包裝類型和string類型之間
除此之外的類型轉(zhuǎn)換我們可以通過定義表達(dá)式來進行指定轉(zhuǎn)換。
3.2、字段名不一致
實體類我們還是沿用 User
;被映射對象 UserVO4
改為:
@Data @NoArgsConstructor @AllArgsConstructor @Builder public class UserVO4 { // 實體類該屬性名是id private String userId; // 實體類該屬性名是name private String userName; private String createTime; private String updateTime; }
那么我們定義的接口就要稍稍修改一下了:
@Mappings({ @Mapping(source = "id", target = "userId"), @Mapping(source = "name", target = "userName") }) UserVO4 toConvertVO(User source); User fromConvertEntity(UserVO4 userVO4);
通過IDE的反編譯功能查看編譯后的實現(xiàn)類,編譯后的結(jié)果是這樣子的:
很明顯, mapstruct
通過讀取我們配置的字段名對應(yīng)關(guān)系,幫我們把它們賦值在了相對應(yīng)的位置上,可以說是相當(dāng)優(yōu)秀了,但這也僅僅是優(yōu)秀,而更秀的還請繼續(xù)往下看:
阿淼小結(jié):
當(dāng)字段名不一致時,通過使用 @Mappings
注解指定對應(yīng)關(guān)系,編譯后即可實現(xiàn)對應(yīng)字段的賦值。
3.3、屬性是枚舉類型
實體類我們還是改用 UserEnum
:
@Data @NoArgsConstructor @AllArgsConstructor @Builder public class UserEnum { private Integer id; private String name; private UserTypeEnum userTypeEnum; }
被映射對象 UserVO5
改為:
@Data @NoArgsConstructor @AllArgsConstructor @Builder public class UserVO5 { private Integer id; private String name; private String type; }
枚舉對象是:
@Getter @AllArgsConstructor public enum UserTypeEnum { Java("000", "Java開發(fā)工程師"), DB("001", "數(shù)據(jù)庫管理員"), LINUX("002", "Linux運維員"); private String value; private String title; }
那么我們定義的接口還是照常定義,不會受到它是枚舉就有所變化:
@Mapping(source = "userTypeEnum", target = "type") UserVO5 toConvertVO5(UserEnum source); UserEnum fromConvertEntity5(UserVO5 userVO5);
通過IDE的反編譯功能查看編譯后的實現(xiàn)類,編譯后的結(jié)果是這樣子的:
很明顯, mapstruct
通過枚舉類型的內(nèi)容,幫我們把枚舉類型轉(zhuǎn)換成字符串,并給type賦值,可謂是小心使得萬年船啊。看來這巧媳婦兒不僅僅優(yōu)秀還心細(xì)啊……
源代碼
文章中的所有例子已上傳github:https://github.com/mmzsblog/mapstructDemo
到此這篇關(guān)于詳解Java中的mapstruct使用的文章就介紹到這了,更多相關(guān)Java mapstruct使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java instanceof和getClass()區(qū)別實例解析
這篇文章主要介紹了Java instanceof和getClass()區(qū)別實例解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-07-07mybatis多個接口參數(shù)的注解使用方式(@Param)
這篇文章主要介紹了mybatis多個接口參數(shù)的注解使用方式(@Param),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-10-10IntelliJ IDEA安裝目錄和設(shè)置目錄的說明(IntelliJ IDEA快速入門)
這篇文章主要介紹了IntelliJ IDEA安裝目錄和設(shè)置目錄的說明(IntelliJ IDEA快速入門),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-04-04使用arthas命令redefine實現(xiàn)Java熱更新(推薦)
今天分享一個非常重要的命令 redefine ,主要作用是加載外部的 .class 文件,用來替換 JVM 已經(jīng)加載的類,總結(jié)起來就是實現(xiàn)了 Java 的熱更新,感興趣的朋友跟隨小編一起看看吧2020-05-05