Spring Boot 2 實戰:集成 MapStruct 類型轉換神器

MapStruct.png

1. 痛點

一種框架的出現都要解決個痛點,我想下面這這種不方便的操做常常有人寫吧。假如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 性能最高。原理相似於lombokMapStruct都是在編譯期進行實現,並且基於GetterSetter,沒有使用反射因此通常不存在運行時性能問題。​​diff.pngapache

今天就搞一搞MapStruct, 並跟Spring Boot 2.x 集成如下。 不管是idea 仍是eclipse 都建議安裝 MapStruct Plugin 插件,固然不安裝也是能夠的。編程

2. Spring Boot 2.1.9 集成 MapStruct

在 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>複製代碼

​​

3. 使用MapStruct

咱們把開始的痛點解決一下。看看 MapStruct 如何下降你的編程成本。安全

3.1 編寫轉換源到目標的映射

編寫CarCarDTO的映射:markdown

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);
 
 }
複製代碼

3.2 MapStruct映射方法講解

上面短短几行代碼就能夠了十分簡單!解釋一下操做步驟:mybatis

首先聲明一個映射接口用@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 最終調用的是 settergetter 方法,而非反射。這也是其性能比較好的緣由之一。numberOfSeats 映射到 seatCount 就比較好理解了。咱們是否是忘記了一個屬性 make,由於他們的位置且名稱徹底一致,因此能夠省略。並且對於包裝類是自動拆箱封箱操做的,而且是線程安全的。MapStruct不僅僅有這些功能,還有其餘一些複雜的功能:

設置轉換默認值和常量。當目標值是 null 時咱們能夠設置其默認值,注意這些都是基本類型以及對應都 boxing 類型,以下

@Mapping(target = "stringProperty", source = "stringProp", defaultValue = "undefined")複製代碼

須要注意的是常量不能對源進行引用(不能指定 source 屬性),下面是正確的操做:

@Mapping(target = "stringConstant", constant = "Constant Value")複製代碼

3.2 Mapper 編譯

當你的應用編譯後。你會在編譯後的目錄好比 maven是 targetgenerated-sourcesannotations 下的子目錄發現生成了一個實現類 好比 咱們上面的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 格式化操做

格式化也是咱們常用的操做,好比數字格式化,日期格式化。這是處理數字格式化的操做,遵循java.text.DecimalFormat的規範:

@Mapping(source = "price", numberFormat = "$#.00")複製代碼


下面展現了將一個日期集合映射到日期字符串集合的格式化操做上:


@IterableMapping(dateFormat = "dd.MM.yyyy")
   List<String> stringListToDateList(List<Date> dates);複製代碼

4.2 使用 java 表達式

下面演示如何使用LocalDateTime 做爲當前的時間值注入 addTime 屬性中。

首先在@org.mapstruct.Mapperimports 屬性中導入 LocalDateTime,該屬性是數組意味着你能夠根據須要導入更多的處理類:

@Mapper(imports = {LocalDateTime.class})複製代碼

接下來只須要在對應的方法上添加註解@org.mapstruct.Mapping ,其屬性expression 接收一個 java() 包括的表達式:

  • 無入參版本:
@Mapping(target = "addTime", expression = "java(LocalDateTime.now())")複製代碼


  • 攜帶入參的版本, 咱們將 Car 的出廠日期字符串manufactureDateStr 注入到 CarDTOLocalDateTime 類型屬性addTime 中去:
@Mapping(target = "addTime", expression = "java(LocalDateTime.parse(car.manufactureDateStr))")
   CarDTO carToCarDTO(Car car);複製代碼


4.3 MapStruct 轉換 Mapper 注入Spring IoC 容器

若是使用要把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);
   
   }
複製代碼

​​

5.總結

其實MapStruct 還有不少的功能。可是從可讀性來講,我建議使用以上幾種容易理解的功能便可。若是你感興趣能夠去mapstruct.org進一步學習。配合lombok和我介紹的jsr303,讓你更加專一於業務,並且代碼更加清晰。

關注公衆號:Felordcn獲取更多資訊

我的博客:https://felord.cn

相關文章
相關標籤/搜索