第三十章:SpringBoot使用MapStruct自動對映DTO
MapStruct
是一種型別安全的bean對映
類生成java註釋處理器。
我們要做的就是定義一個對映器介面,宣告任何必需的對映方法。在編譯的過程中,MapStruct
會生成此介面的實現。該實現使用純java方法呼叫的源和目標物件之間的對映,MapStruct
節省了時間,通過生成程式碼完成繁瑣和容易出錯的程式碼邏輯。下面我們來揭開它的神祕面紗
本章目標
基於SpringBoot
平臺完成MapStruct
對映框架的整合。
SpringBoot 企業級核心技術學習專題
專題 | 專題名稱 | 專題描述 |
---|---|---|
001 | 講解SpringBoot一些企業級層面的核心元件 | |
002 | Spring Boot 核心技術簡書每一篇文章碼雲對應原始碼 | |
003 | 對Spring Cloud核心技術全面講解 | |
004 | Spring Cloud 核心技術簡書每一篇文章對應原始碼 | |
005 | 全面講解QueryDSL核心技術以及基於SpringBoot整合SpringDataJPA |
構建專案
我們使用idea開發工具建立一個SpringBoot
專案,新增相應的依賴,pom.xml配置檔案如下所示:
...省略部分程式碼
<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外掛形式配置,配置如下所示:
...引用官方文件
...
<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相關介面。
資料庫連線資訊
在resource下新建立一個application.yml檔案,並新增如下資料庫連線配置:
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
,表資訊如下所示:
--商品型別資訊表
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。
商品型別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的程式碼如下所示:
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
註解我們用到了兩個屬性,分別是source
、target
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
為我們自動添加了@Component
Spring宣告式注入註解配置。
執行測試
下面我們來建立一個測試的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內我們注入了GoodInfoJPA
、GoodTypeJPA
以及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/