Java实体映射工具MapStruct
聲明:
1、DO(業務實體對象),DTO(數據傳輸對象)。
2、我的代碼中用到了 Lombok ,不了解的可以自行了解一下,了解的忽略這條就好。
1
2
3
在一個成熟的工程中,尤其是現在的分布式系統中,應用與應用之間,還有單獨的應用細分模塊之后,DO 一般不會讓外部依賴,這時候需要在提供對外接口的模塊里放 DTO 用于對象傳輸,也即是 DO 對象對內,DTO對象對外,DTO 可以根據業務需要變更,并不需要映射 DO 的全部屬性。
這種 對象與對象之間的互相轉換,就需要有一個專門用來解決轉換問題的工具,畢竟每一個字段都 get/set 會很麻煩。
MapStruct 就是這樣的一個屬性映射工具,只需要定義一個 Mapper 接口,MapStruct 就會自動實現這個映射接口,避免了復雜繁瑣的映射實現。MapStruct官網地址: http://mapstruct.org/
工程中引入 maven 依賴
<properties>
? ? <mapstruct.version>1.2.0.Final</mapstruct.version>
</properties>
<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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
基本映射
這里定義兩個 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;
? ? /**
? ? ?* 對應 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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
寫一個 Mapper 接口 PersonConverter,其中兩個方法,一個是單實體映射,另一個是List映射
若源對象屬性與目標對象屬性名字一致,會自動映射對應屬性,不一樣的需要指定,也可以用 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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
編譯MapStruct之后,手工編譯或者啟動 IDE 的時候 IDE 也會幫我們編譯, 會自動在 target/classes 下生成對應的實現類
手工編譯命令
mvn compile
1
2
注意!!!下面這個 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;
? ? ? ? ? ? }
? ? ? ? }
? ? }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
寫一個單元測試類 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);
? ? }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
多對一
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
創建 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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
創建測試類,講 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());
? ? }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
可以添加自定義方法
// 形式如下?
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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#### 如果已經有了接收對象,更新目標對象
// 比如在 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());
1
2
3
4
5
6
7
8
9
10
11
Spring 注入的方式
// 剛才一直寫的例子是默認的方式
PersonConverter INSTANCE = Mappers.getMapper(PersonConverter.class);
1
2
還有一種常用的方式,是和常用的框架 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);
}
1
2
3
4
5
6
7
8
9
10
11
這時候測試類改一下,我用的 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);
? ? }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
我 test 路徑下加入了一個配置類
@EnableAutoConfiguration
@Configuration
@ComponentScan
public class BaseTestConfiguration {
}
1
2
3
4
5
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 用于繼承配置
1
2
3
4
5
6
7
8
9
10
11
12
本文只是寫了一些常用的比較簡單的一些功能,更詳細的可以去閱讀官方文檔: http://mapstruct.org/documentation/stable/reference/html/
————————————————
版權聲明:本文為CSDN博主「志哥談笑間」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/zhige_me/article/details/80699784
總結
以上是生活随笔為你收集整理的Java实体映射工具MapStruct的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ES中的RollUp概念
- 下一篇: 一个注解搞懂 Sentinel,@Sen