天天看點

【代碼優化】Bean映射之MapStruct

【代碼優化】Bean映射之MapStruct

一、背景

領域模型互相轉換就隻能靠手工的

get()/set()

普遍的做法有以下幾種:

  1. 手工

    get()/set()

  2. 構造器;
  3. BeanUtils

    工具類(

    Apache

    Spring

    都包含該工具類,使用方式稍稍不同);
  4. Builder

    模式。

這些方式都存在一些缺點:耦合性強,手工

get()/set()

經常丢參數,或者搞錯參數值....

本文推薦一種效率較高的方式:MapStruct

二、理論基礎

MapStruct

是一個自動生成

Bean

映射類的代碼生成器,

MapStruct

還能夠在不同的資料類型之間進行轉換。

2.1 pom.xml

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.4.2.Final</version>
</dependency>
           

2.2 注解關鍵詞

  • @Mapper

    :隻有在接口加上這個注解,

    MapStruct

    才會去實作該接口;
  • @Mapping

    :屬性映射,若源對象屬性與目标對象名字一緻,會自動映射對應屬性:
    1. source

      :源屬性;
    2. target

      :目标屬性;
    3. dateFormat

      :字元串與日期之間互相轉換;
    4. ignore

      : 某個屬性不想映射,可以加上

      ignore=true

    5. expression

      :自定義指定的映射方法;
  • @Mappings

    :配置多個

    @Mapping

  • @MappingTarget

    :映射到現有示例。

2.3 工作原理

我們要做的就是定義一個

mapper

接口,該接口聲明任何所需的映射方法。在編譯期間,

MapStruct

将生成此接口的實作。此實作使用普通的

Java

方法調用來在源對象和目标對象之間進行映射。

三、MapStruct 實踐

3.1 基本準備

  • 新增三個資料庫

    DO

    類:

使用者資訊:

@Data
public class UserInfoDO {

    private Long id;

    private String userName;

    private String password;

    private String phoneNum;

    private Date gmtBroth;

    private RoleDO role;

    public UserInfoDO() {

    }

    public UserInfoDO(RoleDO role,Long id,String userName,String password,String phoneNum,Date gmtBroth) {
        this.role = role;
        this.id = id;
        this.userName = userName;
        this.password = password;
        this.phoneNum = phoneNum;
        this.gmtBroth = gmtBroth;
    }
}
           

使用者補充資訊:

@Data
public class UserExtInfoDO {

    private String favorite;

    public UserExtInfoDO() {

    }

    public UserExtInfoDO(String favorite) {
        this.favorite = favorite;
    }
}
           

角色資訊:

@Data
public class RoleDO {

    private Long id;
    private String roleName;
    private String description;

    public RoleDO() {

    }

    public RoleDO(Long id, String roleName, String description) {
        this.id = id;
        this.roleName = roleName;
        this.description = description;
    }
}
           
  • 新增一個資料傳輸

    DTO

@Data
public class UserInfoDTO {
    /**
     * 使用者id
     */
    private Long userId;
    /**
     * 使用者名
     */
    private String userName;
    /**
     * 使用者名
     */
    private String password;

    /**
     * 生日
     */
    private String brothStr;

    /**
     * 手機号
     */
    private String phoneNum;

    /**
     * 角色名
     */
    private String roleName;

    /**
     * 喜好
     */
    private String favorite;

}
           
  • 新增一個加密工具類
public class Base64Util {

    public static String encode(String str) {
        BASE64Encoder encoder = new BASE64Encoder();
        String encode = encoder.encode(str.getBytes());
        return encode;
    }
}
           
  • 添加映射接口
@Mapper
public interface MapstructConvert {

    /**
     * 擷取該類自動生成的實作類的執行個體
     */
    MapstructConvert INSTANCE = Mappers.getMapper(MapstructConvert.class);
}
           
  1. 添加一個

    interface

    接口,使用

    MapStruct

    @Mapper

    注解修飾;
  2. 使用

    Mappers

    INSTANCE

    執行個體(也可以使用

    Spring

    注入,後面會擴充)。

3.2 一對一映射

  • 自定義轉換時間格式

通過

dateFormat = "xx"

指定映射的日期格式。

  • 指定預設值

如果該值為空,則使用指定的預設值,如:

defaultValue = "-"

  • 忽略不映射的字段

可以通過

ignore = true

指定不需要映射的屬性,如:

@Mapping(target = "password", ignore = true)

  • 嵌套映射

如果一個

DTO

中的值都是從一個對象中的多個嵌套對象映射時,如果不想一個個寫映射,目标可以用

.

表示,如:

@Mapping(source = "role.roleName", target = "roleName")
           
  • 自定義映射

當我們映射

DTO

的時候,如果某些參數的值

MapStruct

的映射配置不能滿足要求,可以使用自定義方法,例如我們對手機号字段借助工具類進行加密後傳回:

@Mapping(target = "phoneNum", expression = "java(cn.van.spring.copy.mapstruct.util.Base64Util.encode(userInfoDO.getPhoneNum()))")
           
  • 完整代碼如下:
@Mappings({
        @Mapping(source = "id", target = "userId"),
        // 自定義轉換時間格式
        @Mapping(source = "gmtBroth", target = "brothStr", dateFormat = "yyyy-MM-dd",defaultValue = "-"),
        // 嵌套映射
        @Mapping(source = "role.roleName", target = "roleName"),
        // 忽略不映射的字段
        @Mapping(target = "password", ignore = true),
        // 自定義映射
        @Mapping(target = "phoneNum", expression = "java(cn.van.spring.copy.mapstruct.util.Base64Util.encode(userInfoDO.getPhoneNum()))"),
})
UserInfoDTO doToDTO(UserInfoDO userInfoDO);
           

3.3 多參數映射

MapStruct

可以将幾種類型的對象映射為另外一種類型,比如将多個

DO

對象轉換為一個

DTO

@Mappings({
            @Mapping(source = "userInfoDO.id", target = "userId"),
            @Mapping(source = "userInfoDO.gmtBroth", target = "brothStr", dateFormat = "yyyy-MM-dd",defaultValue = "-"),
            @Mapping(source = "userInfoDO.role.roleName", target = "roleName"),
            // 忽略不映射的字段
            @Mapping(target = "password", ignore = true),
            // 自定義映射
            @Mapping(target = "phoneNum", expression = "java(cn.van.spring.copy.mapstruct.util.Base64Util.encode(userInfoDO.getPhoneNum()))"),
            @Mapping(source = "userExtInfoDO.favorite", target = "favorite"),
    })
    UserInfoDTO doToDtoMulti(UserInfoDO userInfoDO, UserExtInfoDO userExtInfoDO);
           
這樣,我們就可以把

UserInfoDO

UserExtInfoDO

映射為

UserInfoDTO

3.4 集合映射

屬性映射關系基于一對一的映射關系。
List<UserInfoDTO> doSToDTOS(List<UserInfoDO> userInfoDOS);
           

3.5 映射到現有執行個體

上面都是映射并生成一個新的執行個體,如果是想映射到已有的現有執行個體呢?

我們隻需用

@MappingTarget

修飾。

3.6 注入 Spring

上面的示例調用時都是手動建立了一個

MapstructConvert

執行個體,

現在都是

Spring

的生态,

MapStruct

也可以通過

Spring

注入

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

    /**
     * 一對一映射
     * @param userInfoDO
     * @return
     */
    @Mappings({
            @Mapping(source = "id", target = "userId"),
            // 自定義轉換時間格式,如果為空,給予預設值 "-"
            @Mapping(source = "gmtBroth", target = "brothStr", dateFormat = "yyyy-MM-dd",defaultValue = "-"),
            // 嵌套映射
            @Mapping(source = "role.roleName", target = "roleName"),
            // 忽略不映射的字段
            @Mapping(target = "password", ignore = true),
            // 自定義映射
            @Mapping(target = "phoneNum", expression = "java(cn.van.spring.copy.mapstruct.util.Base64Util.encode(userInfoDO.getPhoneNum()))"),
    })
    UserInfoDTO doToDTO(UserInfoDO userInfoDO);
}
           

相較于前者:幹掉了初始化的

INSTANCE

@Mapper

注解加入了

componentModel = "spring"

注意:預設是以覆寫原有值的方式映射的,如果要保留原有的值,使用

ignore

忽略字段即可。

四、總結

  • 與手工編寫映射代碼相比

MapStruct

通過生成繁瑣且易于編寫的代碼來節省時間。遵循約定優于配置方法,

MapStruct

使用合理的預設值,但在配置或實作特殊行為時

技術交流,歡迎掃一掃!

風塵部落格