天天看點

代碼經驗---mapStruct代替BeanUtils進行類之間的屬性拷貝

目錄

前言:

1:如何使用:

1:引入mapStruct和lombok依賴

2:增加轉換器(@Mapper)

3:寫一個轉換方法

4:擷取對象INSTANCE并使用

2:幾個使用的關鍵點:

@Mapper

@Mappings和@Mapping(指定屬性之間的映射關系)

@AfterMapping 和 @MappingTarget (屬性的自定義映射處理)

@BeanMapping(ignoreByDefault 忽略mapstruct預設的映射行為)

@InheritConfiguration

@InheritInverseConfiguration

3:與spring 結合使用

4:舉例子:DTO轉VO

前言:

在我們進行bean類屬性的拷貝的時候,經常使用org.springframework.beans.BeanUtils這個工具類;

當然可能還有apache的BeanUtils;

還有你可以自己寫get,set方法來進行屬性複制拷貝(代碼較多,複雜);

-

BeanUtils缺點:

1:利用反射的機制,性能較差;若是對象的屬性超級多的話,這個缺點會被無線的放大;

2:需要【類型】和【名稱】都一樣才會進行映射,不同名字的還需要自己手動get/set指派;

如果我們需要實作簡單的bean拷貝,選擇spring的bean拷貝功能是個不錯選擇;

當業務量不大時,不管選擇哪個架構都沒什麼問題,隻要功能支援就ok了;

但是當資料量大的時候,可能就需要考慮性能問題了;

mapstruct:

1:開發會浪費時間,因為要寫轉化器類(需要聲明bean的轉換接口);

2:在添加新的字段的時候也要進行方法的修改,很麻煩;

3:但是優點也是明顯的:不需要進行反射,編譯後的代碼其實就是我們經常寫的get/set方法,性能是很高的(性能媲美直接的get/set);

1:如何使用:

1:引入mapStruct和lombok依賴

<org.mapstruct.version>1.3.1.Final</org.mapstruct.version>

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-jdk8</artifactId>
    <version>${org.mapstruct.version}</version>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>${org.mapstruct.version}</version>
    <scope>provided</scope>
</dependency>
           

2:增加轉換器(@Mapper)

建立一個抽象類,或者接口,标注:@Mapper

注意這個Mappeer引入的是:
import org.mapstruct.Mapper;      

3:寫一個轉換方法

方法名字是可以任意的,沒有要求

一般常用 dtoToVo  或者其他的;

4:擷取對象INSTANCE并使用

CarConvert INSTANCE = Mappers.getMapper(CarConvert.class);

2:幾個使用的關鍵點:

@Mapper

  1. 預設映射規則
    1. 【同類型且同名】的屬性,會自動映射;
    2. mapstruct會自動進行類型轉換:
      1. 8種基本類型 和 對應的包裝類型 之間
      2. 8中基本類型(包括他們的包裝類型)和 string 之間
      3. 日期類型和 string 之間

@Mappings和@Mapping(指定屬性之間的映射關系)

  • 用于指定屬性之間的映射關系:
    • 日期格式化:dateFormat="yyyy-MM-dd HH:mm:ss"
    • 數字格式化:numberFormat = "#.00"
    • @Mapping(source = "totalPrice", target = "totalPrice", numberFormat = "#.00")
      @Mapping(source = "createDate", target = "createDate", dateFormat = "yyyy-MM-dd HH:mm:ss")      
  • source 或者 target 多餘的屬性,對方沒有,不會報錯;
  • ignore:用于配置忽略某一個屬性,不想轉換傳回就配置ignore;
    • @Mapping(target = "color", ignore = true)      
  • 不同名字的屬性,也是可以映射的;
    • @Mapping(source = "name", target = "carName")      
  • 屬性是引用對象的映射:
    • 就是說如果一個屬性是引用類型的話:預設是沒有進行複制的;
      • 對象也可以映射的,需要加下面這個配置
      • @Mapping(source = "carDetailDTO", target = "carDetailVO")
              
      • 這個時候,mapstruct會自動去查找,代碼中有沒有把CarDetailDTO 轉為 CarDetailVO的方法;
      • 是以:我們還需要寫這兩個對象的轉換方法;
  • 批量映射:List<CarDTO>---->List<CarVO>

@AfterMapping 和 @MappingTarget (屬性的自定義映射處理)

用于針對mapstruct 處理不了的一些屬性的映射;

在映射的最後一步,對屬性的自定義映射處理;

@BeanMapping(ignoreByDefault 忽略mapstruct預設的映射行為)

  • ignoreByDefault:忽略mapstruct的預設映射行為;隻映射那些配置了@Mapping的屬性
    • 避免不需要的指派,避免屬性覆寫;
    • 就是可能我某一個屬性,不需要你mapstruct給我映射過來;

@InheritConfiguration

中文解釋:用于繼承配置

  • 可用于更新的場景,避免同樣的配置寫多份

@InheritInverseConfiguration

中文解釋:反向繼承配置

  • 反向映射,不用反過來再寫一遍
  • @InheritConfiguration(name = "")       
  • name 指定使用哪一個方法的配置(方法就是你轉換類中寫的那些轉換方法)寫方法的名字
  • 注意:這裡隻繼承@Mapping注解配置,不會繼承@BeanMapping

3:與spring 結合使用

@Autowired

private CarConvert carConvert;

這樣就可以直接使用這個對象的方法:

carConvert.toCarVo()      

這個時候調用上面這個方法是有問題的,

需要在這個類上,跟spring關聯起來:

@Mapper(componentModel = "spring")

public interface CarConvert {

}

實質就是給生成的類加了@Component注解;

那麼這個時候我們原來寫的這句話就不需要了:CarConvert INSTANCE = Mappers.getMapper(CarConvert.class);

直接用【對象.方法】的形式就可以調用方法了;

4:舉例子:DTO轉VO

先建立一些輔助類:

@Data
public class CarDTO {

    private Long id;

    private Double totalPrice;

    private String createDate;

    private String color;

    private String name;

    private CarDetailDTO carDetailDTO;

    private List<PartDTO> partDTOList;
}

@Data
public class CarVO {

    private Long id;

    private Double totalPrice;

    private Date createDate;

    private String color;

    private String carName;

    private CarDetailVO carDetailVO;

    private List<PartVo> partVoList;

    // 判斷partDTOList是否有值
    private Boolean hasPart;
}

@Data
public class CarDetailDTO {

    private Integer id;

    private String code;
}

@Data
public class CarDetailVO {

    private Integer carDetailId;

    private String carDetailCode;
}

@Data
public class PartDTO {

    private String attribute;

    private String attributeName;
}

@Data
public class PartVo {

    private String attribute;

    private String attributeName;
}

/**
 * 寶馬車
 */
@Data
public class BaoMaVO {

    private Long id;

    private Double totalPrice;

    private String brandName;
}

           

建立一個轉換器:

@Mapper(componentModel = "spring")
public interface CarConvert {

    // 如果用了上面那個 @Mapper(componentModel = "spring") ,就是跟spring 整合了,其實就不需要用這中方式調用,這行代碼就可以注釋掉
    CarConvert INSTANCE = Mappers.getMapper(CarConvert.class);

    @Mappings({
            @Mapping(source = "totalPrice", target = "totalPrice", numberFormat = "#.00"),
            @Mapping(source = "createDate", target = "createDate", dateFormat = "yyyy-MM-dd HH:mm:ss"),
            @Mapping(target = "color", ignore = true),
            @Mapping(source = "name", target = "carName"),
            @Mapping(source = "carDetailDTO", target = "carDetailVO") // 引用對象轉換
    })
    CarVO toCarVO(CarDTO carDTO);


    /**
     * 這個方法也可以用于上面那個引用對象轉換
     */
    @Mapping(source = "id", target = "carDetailId")
    @Mapping(source = "code", target = "carDetailCode")
    CarDetailVO carDetailDTOToCarDetailVo(CarDetailDTO carDetailDTO);

    /**
     * 自定義屬性的映射
     *
     * @AfterMapping :表示讓mapstruct在調用完成自動轉換的方法之後,會來自動調用本方法
     * @MappingTarget :表示傳來的carVO對象是已經指派過的
     */
    @AfterMapping
    public default void carDTOToVOAfter(CarDTO carDTO,@MappingTarget CarVO carVO){
        if (!CollectionUtils.isEmpty(carDTO.getPartDTOList())){
            carVO.setHasPart(true);
        }
    }

    /**
     * 集合批量轉換(就是上面這個方法的批量轉換-toCarVO)
     */
    List<CarVO> toCarVOList(List<CarDTO> carDTOList);

    /**
     * 忽略mapstruct 的預設的映射行為
     *
     * 下面這個ignore 就是忽略了 totalPrice 屬性的映射,不讓這個屬性值被映射過來;
     * 但是若是一個類中我們需要忽略的屬性有幾十個,可能要寫幾十行這個@Mapping(ignore=),非常不友善
     *
     * 是以可以通過@BeanMapping 來進行配置
     */
    @Mapping(source = "totalPrice", target = "totalPrice", ignore = true)
    BaoMaVO toBaoMaVO(CarDTO carDTO);

    /**
     * 配置忽略mapstruct的預設映射行為,隻映射那些配置了@Mapping的屬性
     */
    @BeanMapping(ignoreByDefault = true)
    @Mapping(source = "id", target = "id") // 這裡隻映射id屬性
    @Mapping(source = "name", target = "brandName") // 這裡還映射name屬性
    BaoMaVO toBaoMaVO2(CarDTO carDTO);

    /**
     * 更新場景:繼承配置,避免同樣的配置寫多份
     */
    @InheritConfiguration
    void updateBaoMaVo(CarDTO carDTO,@MappingTarget BaoMaVO baoMaVO);

}
           

測試類:

public static void main(String[] args) {

        //批量轉換(List<CarDTO>---->List<CarVO>)
        List<CarDTO> carDTOList = Lists.newArrayList(); //source 假設這裡已經有資料了
        List<CarVO> carVOList = Lists.newArrayList(); // target
        // 以前是用for循環周遊轉換
        carDTOList.forEach(carDTO -> {
            CarVO carVO = CarConvert.INSTANCE.toCarVO(carDTO);
            carVOList.add(carVO);
        });
        // mapstruce 專門給我們提供了一個方法,用于轉換list的
        List<CarVO> carVOListNew = CarConvert.INSTANCE.toCarVOList(carDTOList);

        // @InheritConfiguration 繼承配置
        CarDTO carDTO = new CarDTO();
        //carDTO.setXXX
        //car.set...
        BaoMaVO baoMaVO = CarConvert.INSTANCE.toBaoMaVO2(carDTO);
        System.out.println(baoMaVO); // baoMaVO 沒有修改以前的值
        // 希望通過 carDTO2 中的屬性值,來更新 baoMaVO 中的屬性值
        CarDTO carDTO2 = new CarDTO();
        //carDTO2.setXXX
        CarConvert.INSTANCE.updateBaoMaVo(carDTO2, baoMaVO); // 進行應該
        System.out.println(baoMaVO); // baoMaVO 修改以後的值
    }
           

其實最常用的就兩個:

@Mapping

@Mappings

其他都不太常用,因為我們主要是做bean屬性拷貝的,如果需要拷貝後重新複制,也可以用上面的,但是一般都直接在拷貝後自己寫set方法了,這樣便于閱讀;

https://lux-sun.blog.csdn.net/article/details/113946112

https://blog.csdn.net/zhige_me/article/details/80699784

繼續閱讀