@Mapper 實體-模型映射

domain類是我們用於與數據庫映射的實體類,通常在將實體數據序列化發送到客戶端時,我們不會吧domain類去序列化,而是將domain類轉成一個model,將model序列化作爲響應給瀏覽器的數據。例如,有一個章節類(Chapter):

@Entity
@Table(name = "chapters")
public class Chapter  {
    @Id
    @Column(name = "id", unique = true, nullable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "name", nullable = false)
    @ColumnComment("章節名")
    private String name;
    
    @Column(name = "book_id", nullable = false)
    private Long bookId;
    
    @Column(name = "parent_id")
    @ColumnComment("父章節的id,標示從屬於哪個章節。如果爲null,則表示改章節就是book(頂結點)")
    private Long parentId;
    
    @Column(name = "description", nullable = true)
    private String description;
    
    @Column(name = "sort_num")
    private Integer sortNum;

    // 省略 get/set
}

章節通常是嵌套形成樹狀結構,當我們需要返回一個樹形結構的數據到頁面時,就需要構建一個model,如下:

public class ChapterModel {
    
    private Long id;
    
    private Long bookId;
    
    private Long parentId;
    
    private String groupName;
    
    private Integer sortNum;
    
    private String description;

    private List<ChapterModel> items; // 樹結構的體現
    
    private int level;
    
    private boolean expanded;

    private boolean isArticle;
    
    private boolean ifCanClick;

    private String parentName;
    
    // 省略 get/set
}

那麼問題來了,當我們每次需要處理一個Chapter對象時,都需要創建一個model,將chapter裏的數據對應塞進model裏,抽成方法就是:entityToModel,而@Mapper就是這個作用,它只需要你去創建一個接口或抽象類(ChapterMapper),然後定義這個entityToModel方法(並不需要去實現),因爲它會自動創建繼承類(ChapterMapperImpl),並實現這個方法(entityToModel)。事實上,實現方法的內部同樣是調用簡單set/get,但這爲我們省了不少時間。下面繼續。

首先,在pom文件中引入依賴:

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-jdk8</artifactId>
    <version>1.2.0.Beta2</version>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.2.0.Beta2</version>
</dependency>

創建接口:

@Mapper(componentModel = "spring", uses = {})
public interface ChapterMapper {
    public ChapterModel entityToModel(Chapter chapter);
}

重新生成class文件,執行命令: mvn clean compile(我用的是maven)

可以看到在項目target目錄下,生成了一個generated-sources目錄,下面可以找到ChapterMapperImpl:

Paste_Image.png
查看裏面的代碼,發現entityToModel方法已經被實現了:

Paste_Image.png
細心一點,其實我們可以發現,生成的方法並不夠“智能”,返回的model中只有五個屬性被賦值了(而這五個屬性恰好就是Chapter類的六個屬性中的五個),因爲mapstruct是根據屬性的名字來匹配的,entity中的name屬性,在model裏找不到同名的屬性,所以就忽略了。那麼,如果我們要設置呢?讓name賦值給model中的groupName?另外還有一種更復雜的需求,我們希望根據Chapter的parentId屬性查找父級目錄Chapter,再將父級目錄的name賦值給model的parentName屬性,又該怎麼處理呢?

只需要給方法加一個註解,並相應配置,再添加一個方法(changeToParentName)即可:

    @Autowired
    private ChapterService chapterService;

    @Mappings({
            @Mapping(source = "chapter.name", target = "groupName"),
            @Mapping(source = "chapter.parentId", target = "parentName"),
    })
    public abstract ChapterModel entityToModel(Chapter chapter);

    public String changeToParentName(Long parentId) {
        if (parentId != null) {
            return chapterService.getNameById(parentId);
        }
        return null;
    }

可以看到,因爲要添加一個自定義實現方法changeToParentName,所以我們把接口改成了抽象類

稍微揣摩一下@Mappings({})裏的配置屬性和值,就不難發現改造映射規則的配置方式。

source 屬性的 chapter.name 中的 chapter就是方法的參數名,name當然就是屬性名了。target屬性,就是需要映射的model的屬性名;
changeToParentName這個方法名不是固定的,但是參數值和返回的數據類型卻是固定的。
接下來,我們再次用命令重新清理一下class文件。

mvn clean compile

可以發現:

Paste_Image.png
這個實現方法已經沒什麼問題了!很強勢,很給力。最後就可以在業務層面愉快的使用這個方法啦:

ChapterModel model = chapterMapper.entityToModel(chapter)

更多關於mapstruct的內容(例如@Mapper(componentModel = "spring", uses = {})的具體用法)可以參見官網

作者:東方一號藍
鏈接:https://www.jianshu.com/p/332326f1ed7a
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章