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

MapStruct是一种类型安全的bean映射类生成java注释处理器。 我们要做的就是定义一个映射器接口,声明任何必需的映射方法。在编译的过程中,MapStruct会生成此接口的实现。该实现使用纯java方法调用的源和目标对象之间的映射,MapStruct节省了时间,通过生成代码完成繁琐和容易出错的代码逻辑。下面我们来揭开它的神秘面纱

本章目标

基于SpringBoot平台完成MapStruct映射框架的集成。

SpringBoot 企业级核心技术学习专题

专题

专题名称

专题描述

001

Spring Boot 核心技术

讲解SpringBoot一些企业级层面的核心组件

002

Spring Boot 核心技术章节源码

Spring Boot 核心技术简书每一篇文章码云对应源码

003

Spring Cloud 核心技术

对Spring Cloud核心技术全面讲解

004

Spring Cloud 核心技术章节源码

Spring Cloud 核心技术简书每一篇文章对应源码

005

QueryDSL 核心技术

全面讲解QueryDSL核心技术以及基于SpringBoot整合SpringDataJPA

006

SpringDataJPA 核心技术

全面讲解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注解我们用到了两个属性,分别是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/

本章代码已经上传到码云: SpringBoot配套源码地址:https://gitee.com/hengboy/spring-boot-chapter SpringCloud配套源码地址:https://gitee.com/hengboy/spring-cloud-chapter SpringBoot相关系列文章请访问:目录:SpringBoot学习目录 QueryDSL相关系列文章请访问:QueryDSL通用查询框架学习目录 SpringDataJPA相关系列文章请访问:目录:SpringDataJPA学习目录 感谢阅读!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏耕耘实录

CentOS7中mysql-5.7.21-el7-x86_64.tar.gz版MySQL的安装与配置

版权声明:本文为耕耘实录原创文章,各大自媒体平台同步更新。欢迎转载,转载请注明出处,谢谢

892
来自专栏杨建荣的学习笔记

MySQL中使用undrop来恢复drop的表(上)

MySQL中可以使用编程语言(比如Python)来解析binlog中DML的逆操作来达到闪回的效果,如果数据不多,手工解析也可以。这也是现在大家碰到的很多DML...

4135
来自专栏battcn

一起来学SpringBoot | 第七篇:整合Mybatis

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射,几乎避免了所有的 JDBC 代码和手动设置参数以及获取结果集,使用简单的 X...

1412
来自专栏DannyHoo的专栏

开发后期在各个页面中添加友盟统计

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010105969/article/details/...

3022
来自专栏Albert陈凯

2018-11-19 如何将大规模数据导入Neo4j及导入具体步骤及Demo

博文原地址:https://my.oschina.net/zlb1992/blog/918243

3012
来自专栏Keegan小钢

App项目实战之路(五):服务端篇

近一个月因为忙于其他事情,一直没能抽出时间来更新项目进度。现在,只能趁着国庆期间,赶紧抽空更新下进度。这次,我想简单谈谈服务端的一些东西。

2226
来自专栏「3306 Pai」社区

赞,Percona一天内放出二个版本

Percona 在10月31日 发布对Percona Server 8.0 RC版本。

1192
来自专栏史上最简单的Spring Cloud教程

SpringBoot非官方教程 | 第五篇:springboot整合 beatlsql

BeetSql是一个全功能DAO工具, 同时具有Hibernate 优点 & Mybatis优点功能,适用于承认以SQL为中心,同时又需求工具能自动能生成大量常...

3257
来自专栏杨建荣的学习笔记

dg broker配置的问题及分析 (r7笔记第22天)

今天在配置一个备库的时候碰到了一些问题,话说配置dg broker真没什么特别需要注意的细节了,本身已经给DBA省了很大的事儿了。 但是有时候就是会出现一些稀奇...

3884
来自专栏杨建荣的学习笔记

配置dg broker的问题分析及修复(r6笔记第84天)

最近从同事那儿接手了一套新环境,备库因为服务器问题已经下架,重新配了一台服务器,所以需要搭一套备库,主库已经配置好了,而且同事已经把在主库把dg broker配...

3797

扫码关注云+社区

领取腾讯云代金券