MapStruct 對象值拷貝

介紹

前提

在Java 項目開發中,存在需要連個不同類的對象的轉化的情況,
例如VO與DO 的中同一邏輯對象的值轉換。

@AllArgsConstructor
@Data
public class UserVo {
    private Long id;
    private String username;
    private String password;
    private String phoneNum;
    private String email;
    private Role role;
}
public class UserDo {
    private Long id;
    private String username;
    private String password;
    private String phoneNum;
    private String email;
    private Role role;
}

兩個類中的屬性大致相同,但是應爲所屬的邏輯分層,導致被分成不同的POJO對象,對於業務分層交互中會用到兩個對象的值的交換。
通常的解決方案:

  • 自行書寫轉換類,進行值轉換

    • 優點:轉換方便,靈活度高
    • 缺點:代碼冗餘過高
  • 使用Java內省機制,Apache/Spring 的BeanUtil.copyProperties

    • 優點:代碼簡潔
    • 缺點:轉換的靈活度不高,對於屬性名稱不同的無法轉換,內省基於Java的反射性能較低
  • MapStruct

    • MapStruct 爲兩者的優勢結合,在相對降低代碼繁瑣的同時,保留性能
    • 基於註解形式,簡化代碼冗餘
    • 才用Java 提供的編譯時Process ,在class 生成期 生成代理類,避免Java 運行時的反射帶來的性能損耗

Demo

Order.java

package com.liaojl.test.mapstruct;

public class Order {
    /**
     * 訂單id
     */
    private Long id;
    /**
     * 訂單編號
     */
    private String orderSn;
    /**
     * 收貨人姓名/號碼
     */
    private String receiverKeyword;
    /**
     * 訂單狀態:0->待付款;1->待發貨;2->已發貨;3->已完成;4->已關閉;5->無效訂單
     */
    private Integer status;
    /**
     * 訂單類型:0->正常訂單;1->秒殺訂單
     */
    private Integer orderType;
    /**
     * 訂單來源:0->PC訂單;1->app訂單
     */
    private Integer sourceType;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getOrderSn() {
        return orderSn;
    }

    public void setOrderSn(String orderSn) {
        this.orderSn = orderSn;
    }

    public String getReceiverKeyword() {
        return receiverKeyword;
    }

    public void setReceiverKeyword(String receiverKeyword) {
        this.receiverKeyword = receiverKeyword;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    public Integer getOrderType() {
        return orderType;
    }

    public void setOrderType(Integer orderType) {
        this.orderType = orderType;
    }

    public Integer getSourceType() {
        return sourceType;
    }

    public void setSourceType(Integer sourceType) {
        this.sourceType = sourceType;
    }
}

OrderQueryParam.java

package com.liaojl.test.mapstruct;

public class OrderQueryParam {
    /**
     * 訂單編號
     */
    private String orderSn;
    /**
     * 收貨人姓名/號碼
     */
    private String receiverKeyword;
    /**
     * 訂單狀態:0->待付款;1->待發貨;2->已發貨;3->已完成;4->已關閉;5->無效訂單
     */
    private Integer status;
    /**
     * 訂單類型:0->正常訂單;1->秒殺訂單
     */
    private Integer orderType;
    /**
     * 訂單來源:0->PC訂單;1->app訂單
     */
    private Integer sourceType;

    public String getOrderSn() {
        return orderSn;
    }

    public void setOrderSn(String orderSn) {
        this.orderSn = orderSn;
    }

    public String getReceiverKeyword() {
        return receiverKeyword;
    }

    public void setReceiverKeyword(String receiverKeyword) {
        this.receiverKeyword = receiverKeyword;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    public Integer getOrderType() {
        return orderType;
    }

    public void setOrderType(Integer orderType) {
        this.orderType = orderType;
    }

    public Integer getSourceType() {
        return sourceType;
    }

    public void setSourceType(Integer sourceType) {
        this.sourceType = sourceType;
    }
}

配置轉換類

package com.liaojl.test.mapstruct.mapper;

import com.liaojl.test.mapstruct.Order;
import com.liaojl.test.mapstruct.OrderQueryParam;
import org.mapstruct.Mapper;

@Mapper
public interface OrderMapper {
    OrderQueryParam entity2queryParam(Order order);
}

測試

/**
 * @author: liaojl
 * @date: 2020/5/30 18:09
 * @since: 1.0
 */
public class Test {
    @org.junit.Test
    public void entity2queryParam() {
        Order order = new Order();
        order.setId(12345L);
        order.setOrderSn("orderSn");
        order.setOrderType(0);
        order.setReceiverKeyword("keyword");
        order.setSourceType(1);
        order.setStatus(2);
        OrderMapper mapper = Mappers.getMapper(OrderMapper.class);
        OrderQueryParam orderQueryParam = mapper.entity2queryParam(order);
        assertEquals(orderQueryParam.getOrderSn(), order.getOrderSn());
        assertEquals(orderQueryParam.getOrderType(), order.getOrderType());
        assertEquals(orderQueryParam.getReceiverKeyword(), order.getReceiverKeyword());
        assertEquals(orderQueryParam.getSourceType(), order.getSourceType());
        assertEquals(orderQueryParam.getStatus(), order.getStatus());
    }
}

Java 編譯期 自動生成代理Java代碼
在這裏插入圖片描述

package com.liaojl.test.mapstruct.mapper;

import com.liaojl.test.mapstruct.Order;
import com.liaojl.test.mapstruct.OrderQueryParam;
import javax.annotation.Generated;

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2020-05-30T23:20:57+0800",
    comments = "version: 1.3.1.Final, compiler: javac, environment: Java 11.0.6 (Amazon.com Inc.)"
)
public class OrderMapperImpl implements OrderMapper {

    @Override
    public OrderQueryParam entity2queryParam(Order order) {
        if ( order == null ) {
            return null;
        }

        OrderQueryParam orderQueryParam = new OrderQueryParam();

        orderQueryParam.setOrderSn( order.getOrderSn() );
        orderQueryParam.setReceiverKeyword( order.getReceiverKeyword() );
        orderQueryParam.setStatus( order.getStatus() );
        orderQueryParam.setOrderType( order.getOrderType() );
        orderQueryParam.setSourceType( order.getSourceType() );

        return orderQueryParam;
    }
}

技術點

基於Java Processororg.mapstruct.ap.MappingProcessor

org.mapstruct.Mapper

@org.mapstruct.Mapping註解用來聲明成員屬性的映射。該註解有兩個重要的屬性:

  • source 代表轉換的源。
  • target 代表轉換的目標。

當兩者屬性相同時可以忽略,MapStruct 轉換時終調用的是 target的setter 和 source 的 getter 方法,而非反射。這也是其性能比較好的原因之一。

@Mapper
public interface OrderMapper {
    OrderQueryParam entity2queryParam(Order order);
}

與org.mapstruct.Mapping

針對 target與source 的屬性名稱 存在差異性時,可以進行自定義轉換

  • target的a屬性轉爲source中B的a屬性
  • target的b屬性轉爲source中b1屬性
 @Mapping(target = "a", source = "B.a")
     @Mapping(target = "b", source = "b1")
     Target sourceToTarget(Source source);

支持默認值

@Mapping(target = "stringProperty", source = "stringProp", defaultValue = "undefined")

格式化操作

格式化也是我們經常使用的操作,比如數字格式化,日期格式化。
這是處理數字格式化的操作,遵循java.text.DecimalFormat的規範:

@Mapping(source = "price", numberFormat = "$#.00")

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

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

使用 java 表達式

下面演示如何使用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);

org.mapstruct.Mappings

配置多個bean屬性的映射。

提示:使用Java 8或更高版本時,可以省略@Mappings 包裝器註釋,並直接在一個方法上指定多個@Mapping註釋。

這兩個示例是相等的。


  // before Java 8
  @Mapper
  public interface MyMapper {
      @Mappings({
          @Mapping(source = "first", target = "firstProperty"),
          @Mapping(source = "second", target = "secondProperty")
      })
      HumanDto toHumanDto(Human human);
  }
  

  // Java 8 and later
  @Mapper
  public interface MyMapper {
      @Mapping(source = "first", target = "firstProperty"),
      @Mapping(source = "second", target = "secondProperty")
      HumanDto toHumanDto(Human human);
  }
  

Spring 集成

MapStruct 轉換 Mapper 注入Spring IoC 容器

如果使用要把Mapper 注入Spring IoC 容器我們只需要這麼聲明,不用再構建一個單例,就可以像其他 spring bean一樣對註解的Bean 進行引用了:

 @Mapper(componentModel = "spring")
public interface OrderMapper {
    OrderQueryParam entity2queryParam(Order order);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章