別再寫滿屏的 get & set 了,太 Low!試試 MapStruct 高階玩法!
接上篇,如果你還不知道 MapStruct 是什麼的,建議你看下棧長之前分享的《幹掉 BeanUtils!試試這款 Bean 自動對映工具,真心強大!!》你就清楚了。
上篇介紹了 MapStruct 的基本概念,以及單個物件、物件列表的對映實踐,棧長看了上篇有一些留言,當然,蘿蔔白菜各有所愛,喜歡就用,不喜歡就不用,沒必要爭執,工具好不好,不一定適合所有人,大家開心就好。
這篇來幾個高階點的對映玩法,別再寫滿屏的 get-set 了,太 Low!MapStruct 高階玩法,這篇棧長帶你上正道!
1、自定義對映
當我們對映 DTO 的時候,如果某些引數的值 MapStruct 的對映配置不能滿足要求,可以使用自定義方法。
新增兩個 DTO 類:
UserCustomDTO 類裡面包含了 UserExtDTO 物件。
/** * 微信公眾號:Java技術棧 * @author 棧長 */ @Data public class UserCustomDTO { private String name; private int sex; private boolean married; private String birthday; private String regDate; private UserExtDTO userExtDTO; private String memo; }
/**
* 微信公眾號:Java技術棧
* @author 棧長
*/
@Data
public class UserExtDTO {
private String regSource;
private String favorite;
private String school;
private int kids;
private String memo;
}
自定義對映:
如果 UserExtDTO 物件不想使用預設的對映,可以新增一個該引數的自定義對映方法。
/** * 微信公眾號:Java技術棧 * @author 棧長 */ @Mapper(componentModel = "spring") public interface UserCustomStruct { @Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd") @Mapping(target = "regDate", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(userDO.getRegDate(),\"yyyy-MM-dd HH:mm:ss\"))") @Mapping(source = "userExtDO", target = "userExtDTO") @Mapping(target = "memo", ignore = true) UserCustomDTO toUserCustomDTO(UserDO userDO); default UserExtDTO toUserExtDTO(UserExtDO userExtDO) { UserExtDTO userExtDTO = new UserExtDTO(); userExtDTO.setKids(userExtDO.getKids()); userExtDTO.setFavorite(userExtDO.getFavorite()); // 覆蓋這兩個值 userExtDTO.setRegSource("預設來源"); userExtDTO.setSchool("預設學校"); return userExtDTO; } }
當對映 UserExtDTO 物件的時候,會自動呼叫該介面中的自定義 toUserExtDTO 方法,完成自定義對映。
來看下生成的實現類原始碼:
@Component
public class UserCustomStructImpl implements UserCustomStruct {
public UserCustomStructImpl() {
}
public UserCustomDTO toUserCustomDTO(UserDO userDO) {
if (userDO == null) {
return null;
} else {
UserCustomDTO userCustomDTO = new UserCustomDTO();
if (userDO.getBirthday() != null) {
userCustomDTO.setBirthday((new SimpleDateFormat("yyyy-MM-dd")).format(userDO.getBirthday()));
}
userCustomDTO.setUserExtDTO(this.toUserExtDTO(userDO.getUserExtDO()));
userCustomDTO.setName(userDO.getName());
userCustomDTO.setSex(userDO.getSex());
userCustomDTO.setMarried(userDO.isMarried());
userCustomDTO.setRegDate(DateFormatUtils.format(userDO.getRegDate(), "yyyy-MM-dd HH:mm:ss"));
return userCustomDTO;
}
}
}
沒錯,setUserExtDTO 方法呼叫了 this.toUserExtDTO 自定義方法對映。
Spring Boot 基礎這篇就不介紹了,系列基礎教程和示例原始碼可以看這裡:https://github.com/javastacks/spring-boot-best-practice
測試一下:
/**
* 微信公眾號:Java技術棧
* @author 棧長
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserCustomStructTest {
@Autowired
private UserCustomStruct userCustomStruct;
@Test
public void test1() {
UserExtDO userExtDO = new UserExtDO();
userExtDO.setRegSource("公眾號:Java技術棧");
userExtDO.setFavorite("寫程式碼");
userExtDO.setSchool("社會大學");
userExtDO.setKids(1);
UserDO userDO = new UserDO();
userDO.setName("棧長自定義方法");
userDO.setSex(1);
userDO.setAge(18);
userDO.setBirthday(new Date());
userDO.setPhone("18888888888");
userDO.setMarried(true);
userDO.setRegDate(new Date());
userDO.setMemo("666");
userDO.setUserExtDO(userExtDO);
UserCustomDTO userCustomDTO = userCustomStruct.toUserCustomDTO(userDO);
System.out.println("=====自定義方法=====");
System.out.println(userCustomDTO);
}
}
輸出結果:
可以看到自定義方法覆蓋的兩個值,結果驗證成功。
2、多引數對映
之前介紹的對映方法中只有一個引數,如果有多個引數對映成一個 DTO,該怎麼弄呢?
比如:有兩具單獨的 DO,UserDO、UserAddressDO 對映成 UserMultiDTO。
直接上程式碼:
/**
* 微信公眾號:Java技術棧
* @author 棧長
*/
@Mapper(componentModel = "spring")
public interface UserMultiStruct {
@Mapping(source = "userDO.birthday", target = "birthday", dateFormat = "yyyy-MM-dd")
@Mapping(target = "userDO.regDate", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(user.getRegDate(),\"yyyy-MM-dd HH:mm:ss\"))")
@Mapping(source = "userAddressDO.postcode", target = "postcode")
@Mapping(source = "userAddressDO.address", target = "address")
@Mapping(target = "memo", ignore = true)
UserMultiDTO toUserMultiDTO(UserDO userDO, UserAddressDO userAddressDO);
}
直接使用指定的 物件名.屬性名
形式對映即可。
來看下生成的實現類原始碼:
@Component
public class UserMultiStructImpl implements UserMultiStruct {
public UserMultiStructImpl() {
}
public UserMultiDTO toUserMultiDTO(UserDO userDO, UserAddressDO userAddressDO) {
if (userDO == null && userAddressDO == null) {
return null;
} else {
UserMultiDTO userMultiDTO = new UserMultiDTO();
if (userDO != null) {
if (userDO.getBirthday() != null) {
userMultiDTO.setBirthday((new SimpleDateFormat("yyyy-MM-dd")).format(userDO.getBirthday()));
}
userMultiDTO.setName(userDO.getName());
userMultiDTO.setSex(userDO.getSex());
userMultiDTO.setMarried(userDO.isMarried());
if (userDO.getRegDate() != null) {
userMultiDTO.setRegDate((new SimpleDateFormat()).format(userDO.getRegDate()));
}
}
if (userAddressDO != null) {
userMultiDTO.setPostcode(userAddressDO.getPostcode());
userMultiDTO.setAddress(userAddressDO.getAddress());
}
return userMultiDTO;
}
}
}
測試一下:
/**
* 微信公眾號:Java技術棧
* @author 棧長
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserMultiStructTest {
@Autowired
private UserMultiStruct userMultiStruct;
@Test
public void test1() {
UserDO userDO = new UserDO();
userDO.setName("多引數對映");
userDO.setSex(1);
userDO.setAge(18);
userDO.setBirthday(new Date());
userDO.setPhone("18888888888");
userDO.setMarried(true);
userDO.setRegDate(new Date());
userDO.setMemo("666");
UserAddressDO userAddressDO = new UserAddressDO();
userAddressDO.setProvince("廣東省");
userAddressDO.setCity("深圳市");
userAddressDO.setPostcode("666666");
userAddressDO.setAddress("001號大街Java技術棧公眾號");
userAddressDO.setMemo("地址資訊");
UserMultiDTO userMultiDTO = userMultiStruct.toUserMultiDTO(userDO, userAddressDO);
System.out.println("=====多引數對映=====");
System.out.println(userMultiDTO);
}
}
輸出結果:
個人資訊和地址資訊都輸出來了,結果驗證成功。
本文實戰原始碼完整版已經上傳:
3、巢狀對映
如果一個 DTO 中的值都是從一個物件中的多個巢狀物件對映時,如果不想一個個寫對映,目標可以用 . 表示。
直接上程式碼:
/** * 微信公眾號:Java技術棧 * @author 棧長 */@Mapper(componentModel = "spring")public interface UserNestedStruct { @Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd") @Mapping(target = "regDate", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(userNestedDO.getRegDate(),\"yyyy-MM-dd HH:mm:ss\"))") @Mapping(source = "userAddressDO", target = ".") @Mapping(source = "userExtDO", target = ".") @Mapping(source = "userExtDO.memo", target = "memo") UserNestedDTO toUserNestedDTO(UserNestedDO userNestedDO);}
如果巢狀物件中出現重名的對映衝突,可以手動指定來源哪個巢狀物件。
來看下生成的實現類原始碼:
@Componentpublic class UserNestedStructImpl implements UserNestedStruct { public UserNestedStructImpl() { } public UserNestedDTO toUserNestedDTO(UserNestedDO userNestedDO) { if (userNestedDO == null) { return null; } else { UserNestedDTO userNestedDTO = new UserNestedDTO(); if (userNestedDO.getBirthday() != null) { userNestedDTO.setBirthday((new SimpleDateFormat("yyyy-MM-dd")).format(userNestedDO.getBirthday())); } userNestedDTO.setMemo(this.userNestedDOUserExtDOMemo(userNestedDO)); userNestedDTO.setCity(this.userNestedDOUserAddressDOCity(userNestedDO)); userNestedDTO.setAddress(this.userNestedDOUserAddressDOAddress(userNestedDO)); userNestedDTO.setRegSource(this.userNestedDOUserExtDORegSource(userNestedDO)); userNestedDTO.setFavorite(this.userNestedDOUserExtDOFavorite(userNestedDO)); userNestedDTO.setSchool(this.userNestedDOUserExtDOSchool(userNestedDO)); userNestedDTO.setName(userNestedDO.getName()); userNestedDTO.setSex(userNestedDO.getSex()); userNestedDTO.setMarried(userNestedDO.isMarried()); userNestedDTO.setRegDate(DateFormatUtils.format(userNestedDO.getRegDate(), "yyyy-MM-dd HH:mm:ss")); return userNestedDTO; } } private String userNestedDOUserExtDOMemo(UserNestedDO userNestedDO) { if (userNestedDO == null) { return null; } else { UserExtDO userExtDO = userNestedDO.getUserExtDO(); if (userExtDO == null) { return null; } else { String memo = userExtDO.getMemo(); return memo == null ? null : memo; } } } private String userNestedDOUserAddressDOCity(UserNestedDO userNestedDO) { if (userNestedDO == null) { return null; } else { UserAddressDO userAddressDO = userNestedDO.getUserAddressDO(); if (userAddressDO == null) { return null; } else { String city = userAddressDO.getCity(); return city == null ? null : city; } } } private String userNestedDOUserAddressDOAddress(UserNestedDO userNestedDO) { if (userNestedDO == null) { return null; } else { UserAddressDO userAddressDO = userNestedDO.getUserAddressDO(); if (userAddressDO == null) { return null; } else { String address = userAddressDO.getAddress(); return address == null ? null : address; } } } private String userNestedDOUserExtDORegSource(UserNestedDO userNestedDO) { if (userNestedDO == null) { return null; } else { UserExtDO userExtDO = userNestedDO.getUserExtDO(); if (userExtDO == null) { return null; } else { String regSource = userExtDO.getRegSource(); return regSource == null ? null : regSource; } } } private String userNestedDOUserExtDOFavorite(UserNestedDO userNestedDO) { if (userNestedDO == null) { return null; } else { UserExtDO userExtDO = userNestedDO.getUserExtDO(); if (userExtDO == null) { return null; } else { String favorite = userExtDO.getFavorite(); return favorite == null ? null : favorite; } } } private String userNestedDOUserExtDOSchool(UserNestedDO userNestedDO) { if (userNestedDO == null) { return null; } else { UserExtDO userExtDO = userNestedDO.getUserExtDO(); if (userExtDO == null) { return null; } else { String school = userExtDO.getSchool(); return school == null ? null : school; } } }}
從原始碼可以看到,從巢狀物件來的值都會新增一個方法判斷一下,以避免出現空指定。
測試一下:
/** * 微信公眾號:Java技術棧 * @author 棧長 */@RunWith(SpringRunner.class)@SpringBootTestpublic class UserNestedStructTest { @Autowired private UserNestedStruct userNestedStruct; @Test public void test1() { UserExtDO userExtDO = new UserExtDO(); userExtDO.setRegSource("公眾號:Java技術棧"); userExtDO.setFavorite("寫程式碼"); userExtDO.setSchool("社會大學"); userExtDO.setKids(1); userExtDO.setMemo("擴充套件資訊"); UserAddressDO userAddressDO = new UserAddressDO(); userAddressDO.setProvince("廣東省"); userAddressDO.setCity("深圳市"); userAddressDO.setPostcode("666666"); userAddressDO.setAddress("001號大街Java技術棧公眾號"); userAddressDO.setMemo("地址資訊"); UserNestedDO userNestedDO = new UserNestedDO(); userNestedDO.setName("棧長巢狀對映"); userNestedDO.setSex(1); userNestedDO.setAge(18); userNestedDO.setBirthday(new Date()); userNestedDO.setPhone("18888888888"); userNestedDO.setMarried(true); userNestedDO.setRegDate(new Date()); userNestedDO.setMemo("666"); userNestedDO.setUserExtDO(userExtDO); userNestedDO.setUserAddressDO(userAddressDO); UserNestedDTO userNestedDTO = userNestedStruct.toUserNestedDTO(userNestedDO); System.out.println("=====巢狀對映====="); System.out.println(userNestedDTO); }}
輸出結果:
可以看到巢狀物件值,並且 memo 也是從指定的巢狀物件來的,結果驗證成功。
4、對映現有例項
以上介紹的都是對映並生成一個新的 DTO 例項,如果是已有的現有 DTO 例項呢,該怎麼對映呢?
直接上程式碼:
/** * 微信公眾號:Java技術棧 * @author 棧長 */@Mapper(componentModel = "spring")public interface UserExistStruct { @Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd") @Mapping(target = "regDate", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(userDO.getRegDate(),\"yyyy-MM-dd HH:mm:ss\"))") @Mapping(source = "userExtDO.regSource", target = "registerSource") @Mapping(source = "userExtDO.favorite", target = "favorite") @Mapping(target = "name", ignore = true) @Mapping(target = "memo", ignore = true) void toUserShowDTO(@MappingTarget UserShowDTO userShowDTO, UserDO userDO);}
在方法上新增 DTO 物件引數並使用 @MappingTarget
物件修飾,引數位置可以調換。
來看下生成的實現類原始碼:
@Componentpublic class UserExistStructImpl implements UserExistStruct { public UserExistStructImpl() { } public void toUserShowDTO(UserShowDTO userShowDTO, UserDO userDO) { if (userDO != null) { if (userDO.getBirthday() != null) { userShowDTO.setBirthday((new SimpleDateFormat("yyyy-MM-dd")).format(userDO.getBirthday())); } else { userShowDTO.setBirthday((String)null); } userShowDTO.setRegisterSource(this.userDOUserExtDORegSource(userDO)); userShowDTO.setFavorite(this.userDOUserExtDOFavorite(userDO)); userShowDTO.setSex(userDO.getSex()); userShowDTO.setMarried(userDO.isMarried()); userShowDTO.setRegDate(DateFormatUtils.format(userDO.getRegDate(), "yyyy-MM-dd HH:mm:ss")); } } private String userDOUserExtDORegSource(UserDO userDO) { if (userDO == null) { return null; } else { UserExtDO userExtDO = userDO.getUserExtDO(); if (userExtDO == null) { return null; } else { String regSource = userExtDO.getRegSource(); return regSource == null ? null : regSource; } } } private String userDOUserExtDOFavorite(UserDO userDO) { if (userDO == null) { return null; } else { UserExtDO userExtDO = userDO.getUserExtDO(); if (userExtDO == null) { return null; } else { String favorite = userExtDO.getFavorite(); return favorite == null ? null : favorite; } } }}
userShowDTO 是作為方法引數傳入的,而不是新建立的。
測試一下:
/** * 微信公眾號:Java技術棧 * @author 棧長 */@RunWith(SpringRunner.class)@SpringBootTestpublic class UserExistStructTest { @Autowired private UserExistStruct userExistStruct; @Test public void test1() { UserExtDO userExtDO = new UserExtDO(); userExtDO.setRegSource("公眾號:Java技術棧"); userExtDO.setFavorite("寫程式碼"); userExtDO.setSchool("社會大學"); UserDO userDO = new UserDO(); userDO.setName("棧長"); userDO.setSex(1); userDO.setAge(18); userDO.setBirthday(new Date()); userDO.setPhone("18888888888"); userDO.setMarried(true); userDO.setRegDate(new Date()); userDO.setMemo("666"); userDO.setUserExtDO(userExtDO); System.out.println("=====對映現有例項前====="); UserShowDTO userShowDTO = new UserShowDTO(); userShowDTO.setName("棧長NAME"); userShowDTO.setMemo("棧長MEMO"); System.out.println(userShowDTO); System.out.println("=====對映現有例項後====="); userExistStruct.toUserShowDTO(userShowDTO, userDO); System.out.println(userShowDTO); }}
輸出結果:
可以看到已有 DTO 物件的值及新對映的值,結果驗證成功。
注意:預設是以覆蓋原有值的方式對映的,如果要保留原有 XX 的值,使用 ignore 忽略即可
總結
本文棧長介紹了 MapStruct 的 4 個高階玩法,足以應對各種 Bean 類映射了,其實還有很多複雜的、個性化用法,一篇難以寫完,棧長後面有時間會整理出來,陸續給大家分享。
感興趣的也可以參考官方文件:
另外,棧長一直介紹的是 DO --> DTO 的對映,其實反過來 DTO --> DO、BO 也是一樣的,只是物件名稱不一樣,對映的用法是一樣的,這樣在服務 A 接收到服務 B 過來的 DTO 資料時,可以再進行一次反射對映供業務使用。
本文實戰原始碼完整版已經上傳:
歡迎 Star 學習,後面 Spring Boot 示例都會在這上面提供!
好了,今天的分享就到這裡了,後面棧長會分享更多好玩的 Java 技術和最新的技術資訊,關注公眾號Java技術棧第一時間推送,我也將主流 Java 面試題和參考答案都整理好了,在公眾號後臺回覆關鍵字 "面試" 進行刷題。
最後,覺得我的文章對你用收穫的話,動動小手,給個在看、轉發,原創不易,棧長需要你的鼓勵。
版權宣告: 本文系公眾號 "Java技術棧" 原創,原創實屬不易,轉載、引用本文內容請註明出處,抄襲者一律舉報+投訴,並保留追究其法律責任的權利。
近期熱文推薦:
1.1,000+ 道 Java面試題及答案整理(2021最新版)
2.別在再滿屏的 if/ else 了,試試策略模式,真香!!
3.臥槽!Java 中的 xx ≠ null 是什麼新語法?
4.Spring Boot 2.5 重磅釋出,黑暗模式太炸了!
覺得不錯,別忘了隨手點贊+轉發哦!