1. 痛點
一種框架的出現(xiàn)都要解決個痛點,我想下面這這種不方便的操作經(jīng)常有人寫吧。
假如Car
類是數(shù)據(jù)庫映射類:
??
package cn.felord.mapstruct.entity;
import lombok.Data;
/**
* Car
*
* @author Felordcn
* @since 13:35 2019/10/12
**/
@Data
public class Car {
private String make;
private int numberOfSeats;
private CarType type;
}
CarType
類:
package cn.felord.mapstruct.entity;
import lombok.Data;
/**
* CarType
*
* @author Felordcn
* @since 13:36 2019/10/12
**/
@Data
public class CarType {
private String type;
}
??
CarDTO
是DTO類:
package cn.felord.mapstruct.entity;
import lombok.Data;
/**
* CarDTO
*
* @author Felordcn
* @since 13:37 2019/10/12
**/
@Data
public class CarDTO {
private String make;
private int seatCount;
private String type;
}
??
我們從數(shù)據(jù)庫查詢Car
然后需要轉(zhuǎn)換為CarDTO
,通常我們會這么寫一個方法進行轉(zhuǎn)換:
public CarDTO carToCarDTO(Car car) {
CarDTO carDTO = new CarDTO();
carDTO.setMake(car.getMake());
carDTO.setSeatCount(car.getNumberOfSeats());
carDTO.setType(car.getCarType().getType());
// 有可能更長
return carDTO;
}
??這種寫法非常繁瑣無味酪呻,而且沒有技術(shù)含量眶掌。甚至中間還牽涉了很多類型轉(zhuǎn)換,嵌套之類的繁瑣操作绒瘦,而我們想要的只是建立它們之間的映射關(guān)系而已称簿。有沒有一種通用的映射工具來幫我們搞定這一切。當(dāng)然有而且還不少惰帽。有人說apache的BeanUtil.copyProperties
可以實現(xiàn)憨降,但是性能差而且容易出異常,很多規(guī)范嚴(yán)禁使用這種途徑该酗。以下是對幾種對象映射框架的對比授药,大多數(shù)情況下 MapStruct
性能最高士嚎。原理類似于lombok
,MapStruct
都是在編譯期進行實現(xiàn)悔叽,而且基于Getter
莱衩、Setter
,沒有使用反射所以一般不存在運行時性能問題。
??
今天就搞一搞MapStruct, 并跟Spring Boot 2.x 集成以下娇澎。 無論是idea 還是eclipse 都建議安裝 MapStruct Plugin 插件,當(dāng)然不安裝也是可以的笨蚁。
2. Spring Boot 2.1.9 集成 MapStruct
在 Spring Boot 的 pom.xml
下引入 MapStruct
的 maven 依賴坐標(biāo):
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
<scope>compile</scope>
</dependency>
<!-- other dependencies -->
</dependencies>
??
3. 使用MapStruct
我們把開始的痛點解決一下√俗看看 MapStruct 如何降低你的編程成本括细。
3.1 編寫轉(zhuǎn)換源到目標(biāo)的映射
編寫Car
到CarDTO
的映射:
package cn.felord.mapstruct.mapping;
import cn.felord.mapstruct.entity.Car;
import cn.felord.mapstruct.entity.CarDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
/**
* CarMapping
*
* @author Felordcn
* @since 14 :02 2019/10/12
*/
@Mapper
public interface CarMapping {
/**
* 用來調(diào)用實例 實際開發(fā)中可使用注入Spring 不寫
*/
CarMapping CAR_MAPPING = Mappers.getMapper(CarMapping.class);
/**
* 源類型 目標(biāo)類型 成員變量相同類型 相同變量名 不用寫{@link org.mapstruct.Mapping}來映射
*
* @param car the car
* @return the car dto
*/
@Mapping(target = "type", source = "carType.type")
@Mapping(target = "seatCount", source = "numberOfSeats")
CarDTO carToCarDTO(Car car);
}
3.2 MapStruct映射方法講解
上面短短幾行代碼就可以了十分簡單!解釋一下操作步驟:
首先聲明一個映射接口用@org.mapstruct.Mapper
(不要跟mybatis注解混淆)標(biāo)記戚啥,說明這是一個實體類型轉(zhuǎn)換接口奋单。這里我們聲明了一個 CAR_MAPPING
來方便我們調(diào)用,CarDTO toCarDTO(Car car)
是不是很熟悉猫十, 像mybatis
一樣抽象出我們的轉(zhuǎn)換方法览濒。@org.mapstruct.Mapping
注解用來聲明成員屬性的映射。該注解有兩個重要的屬性:
-
source
代表轉(zhuǎn)換的源拖云。這里就是Car
匾七。 -
target
代表轉(zhuǎn)換的目標(biāo)。這里是CarDTO
江兢。
這里以成員變量的參數(shù)名為依據(jù)昨忆,如果有嵌套比如 Car
里面有個 CarType
類型的成員變量 carType
,其 type
屬性 來映射 CarDTO
中的 type
字符串杉允,我們使用 type.type
來獲取屬性值邑贴。如果有多層以此類推。MapStruct
最終調(diào)用的是 setter
和 getter
方法叔磷,而非反射拢驾。這也是其性能比較好的原因之一。numberOfSeats
映射到 seatCount
就比較好理解了改基。我們是不是忘記了一個屬性 make
繁疤,因為他們的位置且名稱完全一致,所以可以省略秕狰。而且對于包裝類是自動拆箱封箱操作的稠腊,并且是線程安全的。MapStruct不單單有這些功能鸣哀,還有其他一些復(fù)雜的功能:
設(shè)置轉(zhuǎn)換默認值和常量架忌。當(dāng)目標(biāo)值是 null
時我們可以設(shè)置其默認值,注意這些都是基本類型以及對應(yīng)都 boxing
類型我衬,如下
@Mapping(target = "stringProperty", source = "stringProp", defaultValue = "undefined")
需要注意的是常量不能對源進行引用(不能指定 source
屬性)叹放,下面是正確的操作:
@Mapping(target = "stringConstant", constant = "Constant Value")
3.2 Mapper 編譯
當(dāng)你的應(yīng)用編譯后饰恕。你會在編譯后的目錄比如 maven是 target\generated-sources\annotations
下的子目錄發(fā)現(xiàn)生成了一個實現(xiàn)類 比如 我們上面的CarMapping
會生成CarMappingImpl
如下:
package cn.felord.mapstruct.mapping;
import cn.felord.mapstruct.entity.Car;
import cn.felord.mapstruct.entity.CarDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
import javax.annotation.Generated;
import org.springframework.stereotype.Component;
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2019-10-12T15:05:36+0800",
comments = "version: 1.3.0.Final, compiler: javac, environment: Java 1.8.0_222 (Amazon.com Inc.)"
)
@Component
public class CarMappingImpl implements CarMapping {
@Override
public CarDTO carToCarDTO(Car car) {
if ( car == null ) {
return null;
}
CarDTO carDTO = new CarDTO();
carDTO.setType( carCarTypeType( car ) );
carDTO.setSeatCount( car.getNumberOfSeats() );
carDTO.setMake( car.getMake() );
return carDTO;
}
private String carCarTypeType(Car car) {
if ( car == null ) {
return null;
}
CarType carType = car.getCarType();
if ( carType == null ) {
return null;
}
String type = carType.getType();
if ( type == null ) {
return null;
}
return type;
}
}
4. MapStruct 進階操作
下面介紹幾種 MapStruct 的進階操作:
4.1 格式化操作
格式化也是我們經(jīng)常使用的操作,比如數(shù)字格式化井仰,日期格式化埋嵌。
這是處理數(shù)字格式化的操作,遵循java.text.DecimalFormat
的規(guī)范:
@Mapping(source = "price", numberFormat = "$#.00")
下面展示了將一個日期集合映射到日期字符串集合的格式化操作上:
@IterableMapping(dateFormat = "dd.MM.yyyy")
List<String> stringListToDateList(List<Date> dates);
4.2 使用 java 表達式
下面演示如何使用LocalDateTime
作為當(dāng)前的時間值注入 addTime
屬性中俱恶。
首先在@org.mapstruct.Mapper
的 imports
屬性中導(dǎo)入 LocalDateTime
雹嗦,該屬性是數(shù)組意味著你可以根據(jù)需要導(dǎo)入更多的處理類:
@Mapper(imports = {LocalDateTime.class})
接下來只需要在對應(yīng)的方法上添加注解@org.mapstruct.Mapping
,其屬性expression
接收一個 java()
包括的表達式:
- 無入?yún)姹荆?/li>
@Mapping(target = "addTime", expression = "java(LocalDateTime.now())")
- 攜帶入?yún)⒌陌姹? 我們將
Car
的出廠日期字符串manufactureDateStr
注入到CarDTO
的LocalDateTime
類型屬性addTime
中去:
@Mapping(target = "addTime", expression = "java(LocalDateTime.parse(car.manufactureDateStr))")
CarDTO carToCarDTO(Car car);
4.3 MapStruct 轉(zhuǎn)換 Mapper 注入Spring IoC 容器
如果使用要把Mapper 注入Spring IoC 容器我們只需要這么聲明速那,不用再構(gòu)建一個單例俐银,就可以像其他 spring bean一樣對CarMapping 進行引用了:
package cn.felord.mapstruct.mapping;
import cn.felord.mapstruct.entity.Car;
import cn.felord.mapstruct.entity.CarDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
/**
* CarMapping 注入spring 寫法
*
* @author Felordcn
* @since 14 :02 2019/10/12
*/
@Mapper(componentModel = "spring")
public interface CarMapping {
/**
* 用來調(diào)用實例 實際開發(fā)中可使用注入Spring 不寫
*/
// CarMapping CAR_MAPPING = Mappers.getMapper(CarMapping.class);
/**
* 源類型 目標(biāo)類型 成員變量相同類型 相同變量名 不用寫{@link Mapping}來映射
*
* @param car the car
* @return the car dto
*/
@Mapping(target = "type", source = "carType.type")
@Mapping(target = "seatCount", source = "numberOfSeats")
CarDTO carToCarDTO(Car car);
}
??
5.總結(jié)
其實MapStruct 還有很多的功能尿背。但是從可讀性來說端仰,我建議使用以上幾種容易理解的功能即可。如果你感興趣可以去mapstruct.org進一步學(xué)習(xí)田藐。配合lombok和我介紹的jsr303荔烧,讓你更加專注于業(yè)務(wù),而且代碼更加清晰汽久。
關(guān)注公眾號:碼農(nóng)小胖哥鹤竭,獲取更多資訊