第三十章:SpringBoot使用MapStruct自動映射DTO

MapStruct是一種類型安全的bean映射類生成java註釋處理器。
咱們要作的就是定義一個映射器接口,聲明任何須需的映射方法。在編譯的過程當中,MapStruct會生成此接口的實現。該實現使用純java方法調用的源和目標對象之間的映射,MapStruct節省了時間,經過生成代碼完成繁瑣和容易出錯的代碼邏輯。下面咱們來揭開它的神祕面紗html

本章目標

基於SpringBoot平臺完成MapStruct映射框架的集成。java

構建項目

咱們使用idea開發工具建立一個SpringBoot項目,添加相應的依賴,pom.xml配置文件以下所示:mysql

...省略部分代碼
<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <org.mapstruct.version>1.2.0.CR1</org.mapstruct.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <!--<scope>provided</scope>-->
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--mapStruct依賴-->
        <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>
        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
        </dependency>
        
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.31</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
....省略部分代碼

集成MapStruct官方提供了兩種方式,上面配置文件內咱們採用的是直接添加Maven依賴,而官方文檔還提供了另一種方式,採用Maven插件形式配置,配置以下所示:git

...引用官方文檔
...
<properties>
    <org.mapstruct.version>1.2.0.CR1</org.mapstruct.version>
</properties>
...
<dependencies>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct-jdk8</artifactId>
        <version>${org.mapstruct.version}</version>
    </dependency>
</dependencies>
...
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.5.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${org.mapstruct.version}</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>
...

我我的比較喜歡採用第一種方式,不須要配置過多的插件,依賴方式比較方便。
接下來咱們開始配置下數據庫鏈接信息以及簡單的兩張表的SpringDataJPA相關接口。web

數據庫鏈接信息

在resource下新建立一個application.yml文件,並添加以下數據庫鏈接配置:spring

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8
    username: root
    password: 123456
    #最大活躍數
    maxActive: 20
    #初始化數量
    initialSize: 1
    #最大鏈接等待超時時間
    maxWait: 60000
    #打開PSCache,而且指定每一個鏈接PSCache的大小
    poolPreparedStatements: true
    maxPoolPreparedStatementPerConnectionSize: 20
    #經過connectionProperties屬性來打開mergeSql功能;慢SQL記錄
    #connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
    minIdle: 1
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: select 1 from dual
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    #配置監控統計攔截的filters,去掉後監控界面sql將沒法統計,'wall'用於防火牆
    filters: stat, wall, log4j
  jpa:
    properties:
      hibernate:
        show_sql: true
        format_sql: true

有關SpringDataJPA相關的學習請訪問第三章:SpringBoot使用SpringDataJPA完成CRUD,咱們在數據庫內建立兩張表信息分別是商品基本信息表、商品類型表。
兩張表有相應的關聯,咱們在不採用鏈接查詢的方式模擬使用MapStruct,表信息以下所示:sql

--商品類型信息表
CREATE TABLE `good_types` (
  `tgt_id` int(11) NOT NULL AUTO_INCREMENT,
  `tgt_name` varchar(30) DEFAULT NULL,
  `tgt_is_show` int(1) DEFAULT NULL,
  `tgt_order` int(255) DEFAULT NULL,
  PRIMARY KEY (`tgt_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

--商品基本信息表
CREATE TABLE `good_infos` (
  `tg_id` int(11) NOT NULL AUTO_INCREMENT,
  `tg_type_id` int(11) DEFAULT NULL,
  `tg_title` varchar(30) DEFAULT NULL,
  `tg_price` decimal(8,2) DEFAULT NULL,
  `tg_order` int(2) DEFAULT NULL,
  PRIMARY KEY (`tg_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

INSERT INTO `good_types` VALUES ('1', '青菜', '1', '1');
INSERT INTO `good_infos` VALUES ('1', '1', '芹菜', '12.40', '1');

下面咱們根據這兩張表建立對應的實體類。數據庫

商品類型實體

package com.yuqiyu.chapter30.bean;

import lombok.Data;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

/**
 * ========================
 * Created with IntelliJ IDEA.
 * User:恆宇少年
 * Date:2017/8/20
 * Time:11:17
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 */
@Entity
@Table(name = "good_types")
@Data
public class GoodTypeBean
{
    @Id
    @Column(name = "tgt_id")
    private Long id;

    @Column(name = "tgt_name")
    private String name;
    @Column(name = "tgt_is_show")
    private int show;
    @Column(name = "tgt_order")
    private int order;

}

商品基本信息實體

package com.yuqiyu.chapter30.bean;

import lombok.Data;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

/**
 * ========================
 * Created with IntelliJ IDEA.
 * User:恆宇少年
 * Date:2017/8/20
 * Time:11:16
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 */
@Entity
@Table(name = "good_infos")
@Data
public class GoodInfoBean
{
    @Id
    @Column(name = "tg_id")
    private Long id;
    @Column(name = "tg_title")
    private String title;
    @Column(name = "tg_price")
    private double price;
    @Column(name = "tg_order")
    private int order;
    @Column(name = "tg_type_id")
    private Long typeId;
}

接下來咱們繼續建立相關的JPA。apache

商品類型JPA

package com.yuqiyu.chapter30.jpa;

import com.yuqiyu.chapter30.bean.GoodTypeBean;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * ========================
 * Created with IntelliJ IDEA.
 * User:恆宇少年
 * Date:2017/8/20
 * Time:11:24
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 */
public interface GoodTypeJPA
    extends JpaRepository<GoodTypeBean,Long>
{
}

商品信息JPA

package com.yuqiyu.chapter30.jpa;

import com.yuqiyu.chapter30.bean.GoodInfoBean;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * ========================
 * Created with IntelliJ IDEA.
 * User:恆宇少年
 * Date:2017/8/20
 * Time:11:23
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 */
public interface GoodInfoJPA
    extends JpaRepository<GoodInfoBean,Long>
{
    
}

配置MapStruct

到目前爲止咱們的準備工做差很少完成了,下面咱們開始配置使用MapStruct。咱們的最終目的是爲了返回一個自定義的DTO實體,那麼咱們就先來建立這個DTO,DTO的代碼以下所示:tomcat

package com.yuqiyu.chapter30.dto;

import lombok.Data;

/**
 * 轉換Dto
 * ========================
 * Created with IntelliJ IDEA.
 * User:恆宇少年
 * Date:2017/8/20
 * Time:11:25
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 */
@Data
public class GoodInfoDTO
{
    //商品編號
    private String goodId;
    //商品名稱
    private String goodName;
    //商品價格
    private double goodPrice;
    //類型名稱
    private String typeName;
}

能夠看到GoodInfoDTO實體內集成了商品信息、商品類型兩張表內的數據,對應查詢出信息後,咱們須要使用MapStruct自動映射到GoodInfoDTO。

建立Mapper

Mapper這個定義通常是被普遍應用到MyBatis半自動化ORM框架上,而這裏的Mapper跟Mybatis沒有關係。下面咱們先來看下代碼,以下所示:

package com.yuqiyu.chapter30.mapper;

import com.yuqiyu.chapter30.bean.GoodInfoBean;
import com.yuqiyu.chapter30.bean.GoodTypeBean;
import com.yuqiyu.chapter30.dto.GoodInfoDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;

/**
 * 配置映射
 * ========================
 * Created with IntelliJ IDEA.
 * User:恆宇少年
 * Date:2017/8/20
 * Time:11:26
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 */
@Mapper(componentModel = "spring")
//@Mapper
public interface GoodInfoMapper
{
    //public static GoodInfoMapper MAPPER = Mappers.getMapper(GoodInfoMapper.class);

    @Mappings({
            @Mapping(source = "type.name",target = "typeName"),
            @Mapping(source = "good.id",target = "goodId"),
            @Mapping(source = "good.title",target = "goodName"),
            @Mapping(source = "good.price",target = "goodPrice")
    })
    public GoodInfoDTO from(GoodInfoBean good, GoodTypeBean type);
}

能夠看到GoodInfoMapper是一個接口的形式存在的,固然也能夠是一個抽象類,若是你須要在轉換的時候才用個性化的定製的時候能夠採用抽象類的方式,相應的代碼配置官方文檔已經聲明。
@Mapper註解是用於標註接口、抽象類是被MapStruct自動映射的標識,只有存在該註解纔會將內部的接口方法自動實現。
MapStruct爲咱們提供了多種的獲取Mapper的方式,比較經常使用的兩種分別是

默認配置

默認配置,咱們不須要作過多的配置內容,獲取Mapper的方式就是採用Mappers經過動態工廠內部反射機制完成Mapper實現類的獲取。
默認方式獲取Mapper以下所示:

//Mapper接口內部定義
public static GoodInfoMapper MAPPER = Mappers.getMapper(GoodInfoMapper.class);

//外部調用
GoodInfoMapper.MAPPER.from(goodBean,goodTypeBean);
Spring方式配置

Spring方式咱們須要在@Mapper註解內添加componentModel屬性值,配置後在外部能夠採用@Autowired方式注入Mapper實現類完成映射方法調用。
Spring方式獲取Mapper以下所示:

//註解配置
@Mapper(componentModel = "spring")

//注入Mapper實現類
@Autowired
private GoodInfoMapper goodInfoMapper;

//調用
goodInfoMapper.from(goodBean,goodTypeBean);
@Mappings & @Mapping

Mapper接口定義方法上面聲明瞭一系列的註解映射@Mapping以及@Mappings,那麼這兩個註解是用來幹什麼工做的呢?
@Mapping註解咱們用到了兩個屬性,分別是sourcetarget

source表明的是映射接口方法內的參數名稱,若是是基本類型的參數,參數名能夠直接做爲source的內容,若是是實體類型,則能夠採用實體參數名.字段名的方式做爲source的內容,配置如上面GoodInfoMapper內容所示。

target表明的是映射到方法方法值內的字段名稱,配置如上面GoodInfoMapper所示。

查看Mapper實現

下面咱們執行maven compile命令,到target/generated-sources/annotations目錄下查看對應Mapper實現類,實現類代碼以下所示:

package com.yuqiyu.chapter30.mapper;

import com.yuqiyu.chapter30.bean.GoodInfoBean;
import com.yuqiyu.chapter30.bean.GoodTypeBean;
import com.yuqiyu.chapter30.dto.GoodInfoDTO;
import javax.annotation.Generated;
import org.springframework.stereotype.Component;

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2017-08-20T12:52:52+0800",
    comments = "version: 1.2.0.CR1, compiler: javac, environment: Java 1.8.0_111 (Oracle Corporation)"
)
@Component
public class GoodInfoMapperImpl implements GoodInfoMapper {

    @Override
    public GoodInfoDTO from(GoodInfoBean good, GoodTypeBean type) {
        if ( good == null && type == null ) {
            return null;
        }

        GoodInfoDTO goodInfoDTO = new GoodInfoDTO();

        if ( good != null ) {
            if ( good.getId() != null ) {
                goodInfoDTO.setGoodId( String.valueOf( good.getId() ) );
            }
            goodInfoDTO.setGoodName( good.getTitle() );
            goodInfoDTO.setGoodPrice( good.getPrice() );
        }
        if ( type != null ) {
            goodInfoDTO.setTypeName( type.getName() );
        }

        return goodInfoDTO;
    }
}

MapStruct根據咱們配置的@Mapping註解自動將source實體內的字段進行了調用target實體內字段的setXxx方法賦值,而且作出了一切參數驗證。
咱們採用了Spring方式獲取Mapper,在自動生成的實現類上MapStruct爲咱們自動添加了@ComponentSpring聲明式注入註解配置。

運行測試

下面咱們來建立一個測試的Controller,用於訪問具體請求地址時查詢出商品的基本信息以及商品的類型後調用GoodInfoMapper.from(xxx,xxx)方法完成返回GoodInfoDTO實例。Controller代碼實現以下所示:

package com.yuqiyu.chapter30.controller;

import com.yuqiyu.chapter30.bean.GoodInfoBean;
import com.yuqiyu.chapter30.bean.GoodTypeBean;
import com.yuqiyu.chapter30.dto.GoodInfoDTO;
import com.yuqiyu.chapter30.jpa.GoodInfoJPA;
import com.yuqiyu.chapter30.jpa.GoodTypeJPA;
import com.yuqiyu.chapter30.mapper.GoodInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 測試控制器
 * ========================
 * Created with IntelliJ IDEA.
 * User:恆宇少年
 * Date:2017/8/20
 * Time:12:24
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 */
@RestController
public class GoodInfoController
{
    /**
     * 注入商品基本信息jpa
     */
    @Autowired
    private GoodInfoJPA goodInfoJPA;
    /**
     * 注入商品類型jpa
     */
    @Autowired
    private GoodTypeJPA goodTypeJPA;
    /**
     * 注入mapStruct轉換Mapper
     */
    @Autowired
    private GoodInfoMapper goodInfoMapper;

    /**
     * 查詢商品詳情
     * @param id
     * @return
     */
    @RequestMapping(value = "/detail/{id}")
    public GoodInfoDTO detail(@PathVariable("id") Long id)
    {
        //查詢商品基本信息
        GoodInfoBean goodInfoBean = goodInfoJPA.findOne(id);
        //查詢商品類型基本信息
        GoodTypeBean typeBean = goodTypeJPA.findOne(goodInfoBean.getTypeId());
        //返回轉換dto
        return goodInfoMapper.from(goodInfoBean,typeBean);
    }
}

在Controller內咱們注入了GoodInfoJPAGoodTypeJPA以及GoodInfoMapper,在查詢商品詳情方法時作出了映射處理。接下來咱們啓動項目訪問地址http://127.0.0.1:8080/detail/1查看界面輸出效果,以下所示:

{
goodId: "1",
goodName: "芹菜",
goodPrice: 12.4,
typeName: "青菜"
}

能夠看到界面輸出了GoodInfoDTO內的全部字段內容,而且經過from方法將對應配置的target字段賦值。

總結

本章主要講述了基於SpringBoot開發框架上集成MapStruct自動映射框架,完成模擬多表獲取數據後將某一些字段經過@Mapping配置自動映射到DTO實體實例指定的字段內。
MapStruct官方文檔地址:http://mapstruct.org/documentation/dev/reference/html/

本章代碼已經上傳到碼雲:
網頁地址:http://git.oschina.net/jnyqy/lessons
Git地址:https://git.oschina.net/jnyqy/lessons.git
SpringBoot相關係列文章請訪問:目錄:SpringBoot學習目錄
QueryDSL相關係列文章請訪問:QueryDSL通用查詢框架學習目錄
SpringDataJPA相關係列文章請訪問:目錄:SpringDataJPA學習目錄
感謝閱讀!
歡迎加入QQ技術交流羣,共同進步。
QQ技術交流羣

相關文章
相關標籤/搜索