版權聲明:本文爲博主原創文章,轉載不須要博主贊成,只需貼上原文連接便可。 https://blog.csdn.net/zhige_me/article/details/80699784
聲明:
一、DO(業務實體對象),DTO(數據傳輸對象)。
二、個人代碼中用到了 Lombok ,不瞭解的能夠自行了解一下,瞭解的忽略這條就好。
在一個成熟的工程中,尤爲是如今的分佈式系統中,應用與應用之間,還有單獨的應用細分模塊以後,DO 通常不會讓外部依賴,這時候須要在提供對外接口的模塊裏放 DTO 用於對象傳輸,也便是 DO 對象對內,DTO對象對外,DTO 能夠根據業務須要變動,並不須要映射 DO 的所有屬性。html
這種 對象與對象之間的互相轉換,就須要有一個專門用來解決轉換問題的工具,畢竟每個字段都 get/set 會很麻煩。java
MapStruct 就是這樣的一個屬性映射工具,只須要定義一個 Mapper 接口,MapStruct 就會自動實現這個映射接口,避免了複雜繁瑣的映射實現。MapStruct官網地址: http://mapstruct.org/spring
工程中引入 maven 依賴
<properties>
<mapstruct.version>1.2.0.Final</mapstruct.version>
</properties>express
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</dependency>
</dependencies>
基本映射
這裏定義兩個 DO 對象 Person 和 User,其中 user 是 Person 的一個屬性 ,一個 DTO 對象 PersonDTOapache
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Person {
private Long id;
private String name;
private String email;
private Date birthday;
private User user;
}app
@NoArgsConstructor
@AllArgsConstructor
@Data
public class User {
private Integer age;
}框架
@NoArgsConstructor
@AllArgsConstructor
@Data
public class PersonDTO {
private Long id;
private String name;
/**
* 對應 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;dom
}
寫一個 Mapper 接口 PersonConverter,其中兩個方法,一個是單實體映射,另外一個是List映射maven
若源對象屬性與目標對象屬性名字一致,會自動映射對應屬性,不同的須要指定,也能夠用 format 轉成本身想要的類型,也支持表達式的方式,能夠看到像 id、name、email這些名詞一致的我並無指定 source-target,而birthday-birth指定了,轉換格式的 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);
List<PersonDTO> domain2dto(List<Person> people);
}
編譯MapStruct以後,手工編譯或者啓動 IDE 的時候 IDE 也會幫咱們編譯, 會自動在 target/classes 下生成對應的實現類
手工編譯命令
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 List<PersonDTO> domain2dto(List<Person> people) {
if (people == null) {
return null;
} else {
List<PersonDTO> 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<Person> people = new ArrayList<>();
people.add(person);
List<PersonDTO> personDTOs = PersonConverter.INSTANCE.domain2dto(people);
assertNotNull(personDTOs);
}
}
多對一
MapStruct 能夠將幾種類型的對象映射爲另一種類型,好比將多個 DO 對象轉換爲 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;
}
建立 ItemConverter(映射)接口,MapStruct 就會自動實現該接口
@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);
}
建立測試類,講 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);
#### 若是已經有了接收對象,更新目標對象
// 好比在 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 注入的方式
// 剛纔一直寫的例子是默認的方式
PersonConverter INSTANCE = Mappers.getMapper(PersonConverter.class);
還有一種經常使用的方式,是和經常使用的框架 Spring 結合,在 @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 {
//這裏把轉換器裝配進來
@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 註解的關鍵詞 @Mapper 只有在接口加上這個註解, MapStruct 纔會去實現該接口 @Mapper 裏有個 componentModel 屬性,主要是指定實現類的類型,通常用到兩個 default:默認,能夠經過 Mappers.getMapper(Class) 方式獲取實例對象 spring:在接口的實現類上自動添加註解 @Component,可經過 @Autowired 方式注入 @Mapping:屬性映射,若源對象屬性與目標對象名字一致,會自動映射對應屬性 source:源屬性 target:目標屬性 dateFormat:String 到 Date 日期之間相互轉換,經過 SimpleDateFormat,該值爲 SimpleDateFormat 的日期格式 ignore: 忽略這個字段 @Mappings:配置多個@Mapping @MappingTarget 用於更新已有對象 @InheritConfiguration 用於繼承配置 本文只是寫了一些經常使用的比較簡單的一些功能,更詳細的能夠去閱讀官方文檔: http://mapstruct.org/documentation/stable/reference/html/