摘要:聲明業(yè)務(wù)實體對象,數(shù)據(jù)傳輸對象。這種對象與對象之間的互相轉(zhuǎn)換,就需要有一個專門用來解決轉(zhuǎn)換問題的工具,畢竟每一個字段都會很麻煩。就是這樣的一個屬性映射工具,只需要定義一個接口,就會自動實現(xiàn)這個映射接口,避免了復(fù)雜繁瑣的映射實現(xiàn)。
聲明: 1、DO(業(yè)務(wù)實體對象),DTO(數(shù)據(jù)傳輸對象)。 2、我的代碼中用到了 Lombok ,不了解的可以自行了解一下,了解的忽略這條就好。
在一個成熟的工程中,尤其是現(xiàn)在的分布式系統(tǒng)中,應(yīng)用與應(yīng)用之間,還有多帶帶的應(yīng)用細分模塊之后,DO 一般不會讓外部依賴,這時候需要在提供對外接口的模塊里放 DTO 用于對象傳輸,也即是 DO 對象對內(nèi),DTO對象對外,DTO 可以根據(jù)業(yè)務(wù)需要變更,并不需要映射 DO 的全部屬性。
這種 對象與對象之間的互相轉(zhuǎn)換,就需要有一個專門用來解決轉(zhuǎn)換問題的工具,畢竟每一個字段都 get/set 會很麻煩。
MapStruct 就是這樣的一個屬性映射工具,只需要定義一個 Mapper 接口,MapStruct 就會自動實現(xiàn)這個映射接口,避免了復(fù)雜繁瑣的映射實現(xiàn)。MapStruct官網(wǎng)地址:?http://mapstruct.org/
工程中引入 maven 依賴基本映射1.2.0.Final org.mapstruct mapstruct-jdk8 ${mapstruct.version} org.mapstruct mapstruct-processor ${mapstruct.version}
這里定義兩個 DO 對象 Person 和 User,其中 user 是 Person 的一個屬性 ,一個 DTO 對象 PersonDTO
@NoArgsConstructor @AllArgsConstructor @Data public class Person { private Long id; private String name; private String email; private Date birthday; private User user; } @NoArgsConstructor @AllArgsConstructor @Data public class User { private Integer age; } @NoArgsConstructor @AllArgsConstructor @Data public class PersonDTO { private Long id; private String name; /** * 對應(yīng) Person.user.age */ private Integer age; private String email; /** * 與 DO 里面的字段名稱(birthDay)不一致 */ private Date birth; /** * 對 DO 里面的字段(birthDay)進行拓展,dateFormat 的形式 */ private String birthDateFormat; /** * 對 DO 里面的字段(birthDay)進行拓展,expression 的形式 */ private String birthExpressionFormat; }
寫一個 Mapper 接口 PersonConverter,其中兩個方法,一個是單實體映射,另一個是List映射
若源對象屬性與目標(biāo)對象屬性名字一致,會自動映射對應(yīng)屬性,不一樣的需要指定,也可以用 format 轉(zhuǎn)成自己想要的類型,也支持表達式的方式,可以看到像 id、name、email這些名詞一致的我并沒有指定 source-target,而birthday-birth指定了,轉(zhuǎn)換格式的 birthDateFormat 加了dateFormat 或者 birthExpressionFormat 加了 expression,如果某個屬性你不想映射,可以加個 ignore=true
@Mapper public interface PersonConverter { PersonConverter INSTANCE = Mappers.getMapper(PersonConverter.class); @Mappings({ @Mapping(source = "birthday", target = "birth"), @Mapping(source = "birthday", target = "birthDateFormat", dateFormat = "yyyy-MM-dd HH:mm:ss"), @Mapping(target = "birthExpressionFormat", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(person.getBirthday(),"yyyy-MM-dd HH:mm:ss"))"), @Mapping(source = "user.age", target = "age"), @Mapping(target = "email", ignore = true) }) PersonDTO domain2dto(Person person); Listdomain2dto(List people); }
編譯MapStruct之后,手工編譯或者啟動 IDE 的時候 IDE 也會幫我們編譯, 會自動在 target/classes 下生成對應(yīng)的實現(xiàn)類
手工編譯命令 mvn compile
注意!?。∠旅孢@個 PersonConverterImpl 是自動生成的,不是自己寫的!
public class PersonConverterImpl implements PersonConverter { public PersonConverterImpl() { } public PersonDTO domain2dto(Person person) { if (person == null) { return null; } else { PersonDTO personDTO = new PersonDTO(); personDTO.setBirth(person.getBirthday()); if (person.getBirthday() != null) { personDTO.setBirthDateFormat((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).format(person.getBirthday())); } Integer age = this.personUserAge(person); if (age != null) { personDTO.setAge(age); } personDTO.setId(person.getId()); personDTO.setName(person.getName()); personDTO.setBirthExpressionFormat(DateFormatUtils.format(person.getBirthday(), "yyyy-MM-dd HH:mm:ss")); return personDTO; } } public Listdomain2dto(List people) { if (people == null) { return null; } else { List list = new ArrayList(people.size()); Iterator var3 = people.iterator(); while(var3.hasNext()) { Person person = (Person)var3.next(); list.add(this.domain2dto(person)); } return list; } } private Integer personUserAge(Person person) { if (person == null) { return null; } else { User user = person.getUser(); if (user == null) { return null; } else { Integer age = user.getAge(); return age == null ? null : age; } } } }
寫一個單元測試類 PersonConverterTest 測試一下,看看效果
public class PersonConverterTest { @Test public void test() { Person person = new Person(1L,"zhige","zhige.me@gmail.com",new Date(),new User(1)); PersonDTO personDTO = PersonConverter.INSTANCE.domain2dto(person); assertNotNull(personDTO); assertEquals(personDTO.getId(), person.getId()); assertEquals(personDTO.getName(), person.getName()); assertEquals(personDTO.getBirth(), person.getBirthday()); String format = DateFormatUtils.format(personDTO.getBirth(), "yyyy-MM-dd HH:mm:ss"); assertEquals(personDTO.getBirthDateFormat(),format); assertEquals(personDTO.getBirthExpressionFormat(),format); List多對一people = new ArrayList<>(); people.add(person); List personDTOs = PersonConverter.INSTANCE.domain2dto(people); assertNotNull(personDTOs); } }
MapStruct 可以將幾種類型的對象映射為另外一種類型,比如將多個 DO 對象轉(zhuǎn)換為 DTO
例子
兩個 DO 對象 Item 和 Sku,一個 DTO 對象 SkuDTO
@NoArgsConstructor @AllArgsConstructor @Data public class Item { private Long id; private String title; } @NoArgsConstructor @AllArgsConstructor @Data public class Sku { private Long id; private String code; private Integer price; } @NoArgsConstructor @AllArgsConstructor @Data public class SkuDTO { private Long skuId; private String skuCode; private Integer skuPrice; private Long itemId; private String itemName; }
創(chuàng)建 ItemConverter(映射)接口,MapStruct 就會自動實現(xiàn)該接口
@Mapper public interface ItemConverter { ItemConverter INSTANCE = Mappers.getMapper(ItemConverter.class); @Mappings({ @Mapping(source = "sku.id",target = "skuId"), @Mapping(source = "sku.code",target = "skuCode"), @Mapping(source = "sku.price",target = "skuPrice"), @Mapping(source = "item.id",target = "itemId"), @Mapping(source = "item.title",target = "itemName") }) SkuDTO domain2dto(Item item, Sku sku); }
創(chuàng)建測試類,講 Item 和 Sku 兩個 DO對象,映射成一個 DTO 對象 SkuDTO
public class ItemConverterTest { @Test public void test() { Item item = new Item(1L, "iPhone X"); Sku sku = new Sku(2L, "phone12345", 1000000); SkuDTO skuDTO = ItemConverter.INSTANCE.domain2dto(item, sku); assertNotNull(skuDTO); assertEquals(skuDTO.getSkuId(),sku.getId()); assertEquals(skuDTO.getSkuCode(),sku.getCode()); assertEquals(skuDTO.getSkuPrice(),sku.getPrice()); assertEquals(skuDTO.getItemId(),item.getId()); assertEquals(skuDTO.getItemName(),item.getTitle()); } }可以添加自定義方法
// 形式如下 default PersonDTO personToPersonDTO(Person person) { //hand-written mapping logic } // 比如在 PersonConverter 里面加入如下 default Boolean convert2Bool(Integer value) { if (value == null || value < 1) { return Boolean.FALSE; } else { return Boolean.TRUE; } } default Integer convert2Int(Boolean value) { if (value == null) { return null; } if (Boolean.TRUE.equals(value)) { return 1; } return 0; } // 測試類 PersonConverterTest 加入 assertTrue(PersonConverter.INSTANCE.convert2Bool(1)); assertEquals((int)PersonConverter.INSTANCE.convert2Int(true),1);
#### 如果已經(jīng)有了接收對象,更新目標(biāo)對象
// 比如在 PersonConverter 里面加入如下,@InheritConfiguration 用于繼承剛才的配置 @InheritConfiguration(name = "domain2dto") void update(Person person, @MappingTarget PersonDTO personDTO); // 測試類 PersonConverterTest 加入如下 Person person = new Person(1L,"zhige","zhige.me@gmail.com",new Date(),new User(1)); PersonDTO personDTO = PersonConverter.INSTANCE.domain2dto(person); assertEquals("zhige", personDTO.getName()); person.setName("xiaozhi"); PersonConverter.INSTANCE.update(person, personDTO); assertEquals("xiaozhi", personDTO.getName());Spring 注入的方式
// 剛才一直寫的例子是默認(rèn)的方式 PersonConverter INSTANCE = Mappers.getMapper(PersonConverter.class);
還有一種常用的方式,是和常用的框架 Spring 結(jié)合,在 @Mapper 后面加入 componentModel="spring"
@Mapper(componentModel="spring") public interface PersonConverter { @Mappings({ @Mapping(source = "birthday", target = "birth"), @Mapping(source = "birthday", target = "birthDateFormat", dateFormat = "yyyy-MM-dd HH:mm:ss"), @Mapping(target = "birthExpressionFormat", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(person.getBirthday(),"yyyy-MM-dd HH:mm:ss"))"), @Mapping(source = "user.age", target = "age"), @Mapping(target = "email", ignore = true) }) PersonDTO domain2dto(Person person); }
這時候測試類改一下,我用的 spring boot 的形式
@RunWith(SpringRunner.class) @SpringBootTest(classes = BaseTestConfiguration.class) public class PersonConverterTest { //這里把轉(zhuǎn)換器裝配進來 @Autowired private PersonConverter personConverter; @Test public void test() { Person person = new Person(1L,"zhige","zhige.me@gmail.com",new Date(),new User(1)); PersonDTO personDTO = personConverter.domain2dto(person); assertNotNull(personDTO); assertEquals(personDTO.getId(), person.getId()); assertEquals(personDTO.getName(), person.getName()); assertEquals(personDTO.getBirth(), person.getBirthday()); String format = DateFormatUtils.format(personDTO.getBirth(), "yyyy-MM-dd HH:mm:ss"); assertEquals(personDTO.getBirthDateFormat(),format); assertEquals(personDTO.getBirthExpressionFormat(),format); } }
我 test 路徑下加入了一個配置類
@EnableAutoConfiguration @Configuration @ComponentScan public class BaseTestConfiguration { }MapStruct 注解的關(guān)鍵詞
@Mapper 只有在接口加上這個注解, MapStruct 才會去實現(xiàn)該接口 @Mapper 里有個 componentModel 屬性,主要是指定實現(xiàn)類的類型,一般用到兩個 default:默認(rèn),可以通過 Mappers.getMapper(Class) 方式獲取實例對象 spring:在接口的實現(xiàn)類上自動添加注解 @Component,可通過 @Autowired 方式注入 @Mapping:屬性映射,若源對象屬性與目標(biāo)對象名字一致,會自動映射對應(yīng)屬性 source:源屬性 target:目標(biāo)屬性 dateFormat:String 到 Date 日期之間相互轉(zhuǎn)換,通過 SimpleDateFormat,該值為 SimpleDateFormat 的日期格式 ignore: 忽略這個字段 @Mappings:配置多個@Mapping @MappingTarget 用于更新已有對象 @InheritConfiguration 用于繼承配置
本文只是寫了一些常用的比較簡單的一些功能,更詳細的可以去閱讀官方文檔:? http://mapstruct.org/document...
如果覺得內(nèi)容還不錯,可以關(guān)注一下我哦
微信公眾號:志哥 (ID: zhige-me)
期待與你相遇,一同成長前行!
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/71214.html
摘要:商品類型實體恒宇少年碼云商品基本信息實體恒宇少年碼云接下來我們繼續(xù)創(chuàng)建相關(guān)的。注解是用于標(biāo)注接口抽象類是被自動映射的標(biāo)識,只有存在該注解才會將內(nèi)部的接口方法自動實現(xiàn)。 MapStruct是一種類型安全的bean映射類生成java注釋處理器。我們要做的就是定義一個映射器接口,聲明任何必需的映射方法。在編譯的過程中,MapStruct會生成此接口的實現(xiàn)。該實現(xiàn)使用純java方法調(diào)用的源和目...
摘要:性能大比拼簡介拷貝在工作中被大量使用,可以大幅度的提高工作量。本文對常用的工具進行了壓力測試,方便大家選擇更加適合自己的工具。本篇文章是增強介紹續(xù)篇,該專欄會持續(xù)更新,感興趣的朋友請訂閱我們。的表現(xiàn)反而比更好,可能是模型不一樣導(dǎo)致的。 Java Bean Copy 性能大比拼 簡介 Bean 拷貝在工作中被大量使用,可以大幅度的提高工作量。本文對常用的 Bean copy 工具進行了...
摘要:簡介項目基于的前后端分離的管理系統(tǒng),項目采用分模塊開發(fā)方式,權(quán)限控制采用,基于角色的訪問控制,支持?jǐn)?shù)據(jù)字典數(shù)據(jù)權(quán)限管理前端菜單支持動態(tài)路由,另外還有其他的功能模塊日志管理代碼生成器系統(tǒng)監(jiān)控云存儲管理系統(tǒng)工具等等。 簡介 項目基于 Spring Boot 2.1.0 、 Spring Data JPA、 Spring Security、Redis、Vue的前后端分離的管理系統(tǒng),項目采用分...
摘要:打開,,選中,然后再選中,輸入項目的和,指定等配置,修改,打開項目,添加一些必要的目錄,最終項目框架目錄圖如下修改文件,指定各依賴和插件的版本等信息在標(biāo)簽里面管理各依賴的版本號添加項目依賴管理依賴配置好之后,開始整合。 最近在回顧和總結(jié)一些技術(shù),想到了把之前比較火的 SSM 框架重新搭建出來,作為一個小結(jié),同時也希望本文章寫出來能對大家有一些幫助和啟發(fā),因本人水平有限,難免可能會有一些...
摘要:系列文章地址原文地址一個高性能的數(shù)據(jù)訪問層需要很多關(guān)于數(shù)據(jù)庫的內(nèi)部結(jié)構(gòu)以及很多優(yōu)化商業(yè)應(yīng)用的技術(shù)建議。在語句中的表現(xiàn)最好,不過不能使用約束,數(shù)據(jù)完整性的控制較差。應(yīng)用層的緩存則利用高速副本的方式來保證低響應(yīng)時間。 Github系列文章地址 原文地址 Introduction 一個高性能的數(shù)據(jù)訪問層需要很多關(guān)于數(shù)據(jù)庫的內(nèi)部結(jié)構(gòu)、JDBC、JPA、Hibernate以及很多優(yōu)化商業(yè)應(yīng)用...
閱讀 2259·2021-11-18 10:02
閱讀 3351·2021-11-11 16:55
閱讀 2757·2021-09-14 18:02
閱讀 2520·2021-09-04 16:41
閱讀 2213·2021-09-04 16:40
閱讀 1341·2019-08-30 15:56
閱讀 2283·2019-08-30 15:54
閱讀 3225·2019-08-30 14:15