MapStruct是一種類(lèi)型安全的bean映射類(lèi)生成java注釋處理器。
我們要做的就是定義一個(gè)映射器接口,聲明任何必需的映射方法。在編譯的過(guò)程中,MapStruct會(huì)生成此接口的實(shí)現(xiàn)。該實(shí)現(xiàn)使用純java方法調(diào)用的源和目標(biāo)對(duì)象之間的映射,MapStruct節(jié)省了時(shí)間,通過(guò)生成代碼完成繁瑣和容易出錯(cuò)的代碼邏輯。下面我們來(lái)揭開(kāi)它的神秘面紗
免費(fèi)教程專(zhuān)題
恒宇少年在博客整理三套免費(fèi)學(xué)習(xí)教程專(zhuān)題,由于文章偏多特意添加了閱讀指南,新文章以及之前的文章都會(huì)在專(zhuān)題內(nèi)陸續(xù)填充,希望可以幫助大家解惑更多知識(shí)點(diǎn)。
本章目標(biāo)
基于SpringBoot平臺(tái)完成MapStruct映射框架的集成。
SpringBoot 企業(yè)級(jí)核心技術(shù)學(xué)習(xí)專(zhuān)題
| 專(zhuān)題 | 專(zhuān)題名稱(chēng) | 專(zhuān)題描述 |
|---|---|---|
| 001 | Spring Boot 核心技術(shù) | 講解SpringBoot一些企業(yè)級(jí)層面的核心組件 |
| 002 | Spring Boot 核心技術(shù)章節(jié)源碼 | Spring Boot 核心技術(shù)簡(jiǎn)書(shū)每一篇文章碼云對(duì)應(yīng)源碼 |
| 003 | Spring Cloud 核心技術(shù) | 對(duì)Spring Cloud核心技術(shù)全面講解 |
| 004 | Spring Cloud 核心技術(shù)章節(jié)源碼 | Spring Cloud 核心技術(shù)簡(jiǎn)書(shū)每一篇文章對(duì)應(yīng)源碼 |
| 005 | QueryDSL 核心技術(shù) | 全面講解QueryDSL核心技術(shù)以及基于SpringBoot整合SpringDataJPA |
| 006 | SpringDataJPA 核心技術(shù) | 全面講解SpringDataJPA核心技術(shù) |
| 007 | SpringBoot核心技術(shù)學(xué)習(xí)目錄 | SpringBoot系統(tǒng)的學(xué)習(xí)目錄,敬請(qǐng)關(guān)注點(diǎn)贊?。? |
構(gòu)建項(xiàng)目
我們使用idea開(kāi)發(fā)工具創(chuàng)建一個(gè)SpringBoot項(xiàng)目,添加相應(yīng)的依賴(lài),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依賴(lài)-->
<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官方提供了兩種方式,上面配置文件內(nèi)我們采用的是直接添加Maven依賴(lài),而官方文檔還提供了另外一種方式,采用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>
...
我個(gè)人比較喜歡采用第一種方式,不需要配置過(guò)多的插件,依賴(lài)方式比較方便。
接下來(lái)我們開(kāi)始配置下數(shù)據(jù)庫(kù)連接信息以及簡(jiǎn)單的兩張表的SpringDataJPA相關(guān)接口。
數(shù)據(jù)庫(kù)連接信息
在resource下新創(chuàng)建一個(gè)application.yml文件,并添加如下數(shù)據(jù)庫(kù)連接配置:
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
#最大活躍數(shù)
maxActive: 20
#初始化數(shù)量
initialSize: 1
#最大連接等待超時(shí)時(shí)間
maxWait: 60000
#打開(kāi)PSCache,并且指定每個(gè)連接PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
#通過(guò)connectionProperties屬性來(lái)打開(kāi)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
#配置監(jiān)控統(tǒng)計(jì)攔截的filters,去掉后監(jiān)控界面sql將無(wú)法統(tǒng)計(jì),'wall'用于防火墻
filters: stat, wall, log4j
jpa:
properties:
hibernate:
show_sql: true
format_sql: true
有關(guān)SpringDataJPA相關(guān)的學(xué)習(xí)請(qǐng)?jiān)L問(wèn)第三章:SpringBoot使用SpringDataJPA完成CRUD,我們?cè)跀?shù)據(jù)庫(kù)內(nèi)創(chuàng)建兩張表信息分別是商品基本信息表、商品類(lèi)型表。
兩張表有相應(yīng)的關(guān)聯(lián),我們?cè)诓徊捎眠B接查詢(xún)的方式模擬使用MapStruct,表信息如下所示:
--商品類(lèi)型信息表
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');
下面我們根據(jù)這兩張表創(chuàng)建對(duì)應(yīng)的實(shí)體類(lèi)。
商品類(lèi)型實(shí)體
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;
}
商品基本信息實(shí)體
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;
}
接下來(lái)我們繼續(xù)創(chuàng)建相關(guān)的JPA。
商品類(lèi)型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
到目前為止我們的準(zhǔn)備工作差不多完成了,下面我們開(kāi)始配置使用MapStruct。我們的最終目的是為了返回一個(gè)自定義的DTO實(shí)體,那么我們就先來(lái)創(chuàng)建這個(gè)DTO,DTO的代碼如下所示:
package com.yuqiyu.chapter30.dto;
import lombok.Data;
/**
* 轉(zhuǎn)換Dto
* ========================
* Created with IntelliJ IDEA.
* User:恒宇少年
* Date:2017/8/20
* Time:11:25
* 碼云:http://git.oschina.net/jnyqy
* ========================
*/
@Data
public class GoodInfoDTO
{
//商品編號(hào)
private String goodId;
//商品名稱(chēng)
private String goodName;
//商品價(jià)格
private double goodPrice;
//類(lèi)型名稱(chēng)
private String typeName;
}
可以看到GoodInfoDTO實(shí)體內(nèi)集成了商品信息、商品類(lèi)型兩張表內(nèi)的數(shù)據(jù),對(duì)應(yīng)查詢(xún)出信息后,我們需要使用MapStruct自動(dòng)映射到GoodInfoDTO。
創(chuàng)建Mapper
Mapper這個(gè)定義一般是被廣泛應(yīng)用到MyBatis半自動(dòng)化ORM框架上,而這里的Mapper跟Mybatis沒(méi)有關(guān)系。下面我們先來(lái)看下代碼,如下所示:
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是一個(gè)接口的形式存在的,當(dāng)然也可以是一個(gè)抽象類(lèi),如果你需要在轉(zhuǎn)換的時(shí)候才用個(gè)性化的定制的時(shí)候可以采用抽象類(lèi)的方式,相應(yīng)的代碼配置官方文檔已經(jīng)聲明。
@Mapper注解是用于標(biāo)注接口、抽象類(lèi)是被MapStruct自動(dòng)映射的標(biāo)識(shí),只有存在該注解才會(huì)將內(nèi)部的接口方法自動(dòng)實(shí)現(xiàn)。
MapStruct為我們提供了多種的獲取Mapper的方式,比較常用的兩種分別是
默認(rèn)配置
默認(rèn)配置,我們不需要做過(guò)多的配置內(nèi)容,獲取Mapper的方式就是采用Mappers通過(guò)動(dòng)態(tài)工廠內(nèi)部反射機(jī)制完成Mapper實(shí)現(xiàn)類(lèi)的獲取。
默認(rèn)方式獲取Mapper如下所示:
//Mapper接口內(nèi)部定義
public static GoodInfoMapper MAPPER = Mappers.getMapper(GoodInfoMapper.class);
//外部調(diào)用
GoodInfoMapper.MAPPER.from(goodBean,goodTypeBean);
Spring方式配置
Spring方式我們需要在@Mapper注解內(nèi)添加componentModel屬性值,配置后在外部可以采用@Autowired方式注入Mapper實(shí)現(xiàn)類(lèi)完成映射方法調(diào)用。
Spring方式獲取Mapper如下所示:
//注解配置
@Mapper(componentModel = "spring")
//注入Mapper實(shí)現(xiàn)類(lèi)
@Autowired
private GoodInfoMapper goodInfoMapper;
//調(diào)用
goodInfoMapper.from(goodBean,goodTypeBean);
@Mappings & @Mapping
在Mapper接口定義方法上面聲明了一系列的注解映射@Mapping以及@Mappings,那么這兩個(gè)注解是用來(lái)干什么工作的呢?
@Mapping注解我們用到了兩個(gè)屬性,分別是source、target
source代表的是映射接口方法內(nèi)的參數(shù)名稱(chēng),如果是基本類(lèi)型的參數(shù),參數(shù)名可以直接作為source的內(nèi)容,如果是實(shí)體類(lèi)型,則可以采用實(shí)體參數(shù)名.字段名的方式作為source的內(nèi)容,配置如上面GoodInfoMapper內(nèi)容所示。
target代表的是映射到方法方法值內(nèi)的字段名稱(chēng),配置如上面GoodInfoMapper所示。
查看Mapper實(shí)現(xiàn)
下面我們執(zhí)行maven compile命令,到target/generated-sources/annotations目錄下查看對(duì)應(yīng)Mapper實(shí)現(xiàn)類(lèi),實(shí)現(xiàn)類(lèi)代碼如下所示:
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根據(jù)我們配置的@Mapping注解自動(dòng)將source實(shí)體內(nèi)的字段進(jìn)行了調(diào)用target實(shí)體內(nèi)字段的setXxx方法賦值,并且做出了一切參數(shù)驗(yàn)證。
我們采用了Spring方式獲取Mapper,在自動(dòng)生成的實(shí)現(xiàn)類(lèi)上MapStruct為我們自動(dòng)添加了@ComponentSpring聲明式注入注解配置。
運(yùn)行測(cè)試
下面我們來(lái)創(chuàng)建一個(gè)測(cè)試的Controller,用于訪問(wèn)具體請(qǐng)求地址時(shí)查詢(xún)出商品的基本信息以及商品的類(lèi)型后調(diào)用GoodInfoMapper.from(xxx,xxx)方法完成返回GoodInfoDTO實(shí)例。Controller代碼實(shí)現(xiàn)如下所示:
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;
/**
* 測(cè)試控制器
* ========================
* 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;
/**
* 注入商品類(lèi)型jpa
*/
@Autowired
private GoodTypeJPA goodTypeJPA;
/**
* 注入mapStruct轉(zhuǎn)換Mapper
*/
@Autowired
private GoodInfoMapper goodInfoMapper;
/**
* 查詢(xún)商品詳情
* @param id
* @return
*/
@RequestMapping(value = "/detail/{id}")
public GoodInfoDTO detail(@PathVariable("id") Long id)
{
//查詢(xún)商品基本信息
GoodInfoBean goodInfoBean = goodInfoJPA.findOne(id);
//查詢(xún)商品類(lèi)型基本信息
GoodTypeBean typeBean = goodTypeJPA.findOne(goodInfoBean.getTypeId());
//返回轉(zhuǎn)換dto
return goodInfoMapper.from(goodInfoBean,typeBean);
}
}
在Controller內(nèi)我們注入了GoodInfoJPA、GoodTypeJPA以及GoodInfoMapper,在查詢(xún)商品詳情方法時(shí)做出了映射處理。接下來(lái)我們啟動(dòng)項(xiàng)目訪問(wèn)地址http://127.0.0.1:8080/detail/1查看界面輸出效果,如下所示:
{
goodId: "1",
goodName: "芹菜",
goodPrice: 12.4,
typeName: "青菜"
}
可以看到界面輸出了GoodInfoDTO內(nèi)的所有字段內(nèi)容,并且通過(guò)from方法將對(duì)應(yīng)配置的target字段賦值。
總結(jié)
本章主要講述了基于SpringBoot開(kāi)發(fā)框架上集成MapStruct自動(dòng)映射框架,完成模擬多表獲取數(shù)據(jù)后將某一些字段通過(guò)@Mapping配置自動(dòng)映射到DTO實(shí)體實(shí)例指定的字段內(nèi)。
MapStruct官方文檔地址:http://mapstruct.org/documentation/dev/reference/html/
本章代碼已經(jīng)上傳到碼云:
SpringBoot配套源碼地址:https://gitee.com/hengboy/spring-boot-chapter
SpringCloud配套源碼地址:https://gitee.com/hengboy/spring-cloud-chapter