一種框架的出現都要解決個痛點,我想下面這這種不方便的操做常常有人寫吧。
假如Car
類是數據庫映射類:
java
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
類:spring
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; }
咱們從數據庫查詢Car
而後須要轉換爲CarDTO
,一般咱們會這麼寫一個方法進行轉換:express
public CarDTO carToCarDTO(Car car) { CarDTO carDTO = new CarDTO(); carDTO.setMake(car.getMake()); carDTO.setSeatCount(car.getNumberOfSeats()); carDTO.setType(car.getCarType().getType()); // 有可能更長 return carDTO; }
這種寫法很是繁瑣無味,並且沒有技術含量。甚至中間還牽涉了不少類型轉換,嵌套之類的繁瑣操做,而咱們想要的只是創建它們之間的映射關係而已。有沒有一種通用的映射工具來幫咱們搞定這一切。固然有並且還很多。有人說apache的BeanUtil.copyProperties
能夠實現,可是性能差並且容易出異常,不少規範嚴禁使用這種途徑。如下是對幾種對象映射框架的對比,大多數狀況下 MapStruct
性能最高。原理相似於lombok
,MapStruct
都是在編譯期進行實現,並且基於Getter
、Setter
,沒有使用反射因此通常不存在運行時性能問題。
apache
今天就搞一搞MapStruct, 並跟Spring Boot 2.x 集成如下。 不管是idea 仍是eclipse 都建議安裝 MapStruct Plugin 插件,固然不安裝也是能夠的。編程
在 Spring Boot 的 pom.xml
下引入 MapStruct
的 maven 依賴座標:數組
<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>
咱們把開始的痛點解決一下。看看 MapStruct 如何下降你的編程成本。安全
編寫Car
到CarDTO
的映射:mybatis
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 { /** * 用來調用實例 實際開發中可以使用注入Spring 不寫 */ CarMapping CAR_MAPPING = Mappers.getMapper(CarMapping.class); /** * 源類型 目標類型 成員變量相同類型 相同變量名 不用寫{@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); }
上面短短几行代碼就能夠了十分簡單!解釋一下操做步驟:app
首先聲明一個映射接口用@org.mapstruct.Mapper
(不要跟mybatis註解混淆)標記,說明這是一個實體類型轉換接口。這裏咱們聲明瞭一個 CAR_MAPPING
來方便咱們調用,CarDTO toCarDTO(Car car)
是否是很熟悉, 像mybatis
同樣抽象出咱們的轉換方法。@org.mapstruct.Mapping
註解用來聲明成員屬性的映射。該註解有兩個重要的屬性:
source
表明轉換的源。這裏就是Car
。target
表明轉換的目標。這裏是CarDTO
。這裏以成員變量的參數名爲依據,若是有嵌套好比 Car
裏面有個 CarType
類型的成員變量 carType
,其 type
屬性 來映射 CarDTO
中的 type
字符串,咱們使用 type.type
來獲取屬性值。若是有多層以此類推。MapStruct
最終調用的是 setter
和 getter
方法,而非反射。這也是其性能比較好的緣由之一。numberOfSeats
映射到 seatCount
就比較好理解了。咱們是否是忘記了一個屬性 make
,由於他們的位置且名稱徹底一致,因此能夠省略。並且對於包裝類是自動拆箱封箱操做的,而且是線程安全的。MapStruct不僅僅有這些功能,還有其餘一些複雜的功能:
設置轉換默認值和常量。當目標值是 null
時咱們能夠設置其默認值,注意這些都是基本類型以及對應都 boxing
類型,以下
@Mapping(target = "stringProperty", source = "stringProp", defaultValue = "undefined")
須要注意的是常量不能對源進行引用(不能指定 source
屬性),下面是正確的操做:
@Mapping(target = "stringConstant", constant = "Constant Value")
當你的應用編譯後。你會在編譯後的目錄好比 maven是 target\generated-sources\annotations
下的子目錄發現生成了一個實現類 好比 咱們上面的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; } }
下面介紹幾種 MapStruct 的進階操做:
格式化也是咱們常用的操做,好比數字格式化,日期格式化。
這是處理數字格式化的操做,遵循java.text.DecimalFormat
的規範:
@Mapping(source = "price", numberFormat = "$#.00")
下面展現了將一個日期集合映射到日期字符串集合的格式化操做上:
@IterableMapping(dateFormat = "dd.MM.yyyy") List<String> stringListToDateList(List<Date> dates);
下面演示如何使用LocalDateTime
做爲當前的時間值注入 addTime
屬性中。
首先在@org.mapstruct.Mapper
的 imports
屬性中導入 LocalDateTime
,該屬性是數組意味着你能夠根據須要導入更多的處理類:
@Mapper(imports = {LocalDateTime.class})
接下來只須要在對應的方法上添加註解@org.mapstruct.Mapping
,其屬性expression
接收一個 java()
包括的表達式:
無入參版本:
@Mapping(target = "addTime", expression = "java(LocalDateTime.now())")
攜帶入參的版本, 咱們將 Car
的出廠日期字符串manufactureDateStr
注入到 CarDTO
的 LocalDateTime
類型屬性addTime
中去:
@Mapping(target = "addTime", expression = "java(LocalDateTime.parse(car.manufactureDateStr))") CarDTO carToCarDTO(Car car);
若是使用要把Mapper 注入Spring IoC 容器咱們只須要這麼聲明,不用再構建一個單例,就能夠像其餘 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 { /** * 用來調用實例 實際開發中可以使用注入Spring 不寫 */ // CarMapping CAR_MAPPING = Mappers.getMapper(CarMapping.class); /** * 源類型 目標類型 成員變量相同類型 相同變量名 不用寫{@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); }
其實MapStruct 還有不少的功能。可是從可讀性來講,我建議使用以上幾種容易理解的功能便可。若是你感興趣能夠去mapstruct.org進一步學習。配合lombok和我介紹的jsr303,讓你更加專一於業務,並且代碼更加清晰。
關注公衆號:Felordcn獲取更多資訊