原创

SpringBoot 系列教程(五十五):SpringBoot整合MapStruct自动映射DTO

版权声明: 本文为博主原创文章,转载请注明原文出处!
本文链接:https://thinkingcao.blog.csdn.net/article/details/102907994

MapStruct:  https://mapstruct.org

前言

MapStruct是一个Java 注释处理器,在比较常见的项目开发过程中,前端提交往后端的数据,一部分是不需要存入数据库当中的;后端从数据库中取出的数据,一部分是不可以交给用户的;那么,Po面向的是DB,访问数据库交互,Vo面向的是客户端,封装返回数据到前端的对象;mapstruct就提供了Vo与Po自动转换的一种方式;

一、简介

MapStruct是一个Java 注释处理器,用于生成类型安全的bean映射类。您要做的就是定义一个映射器接口,该接口声明任何必需的映射方法。在编译期间,MapStruct将生成此接口的实现。此实现使用简单的Java方法调用在源对象和目标对象之间进行映射,即没有反射或类似内容。

与手动编写映射代码相比,MapStruct通过生成繁琐且易于出错的代码来节省时间。遵循配置方法上的约定,MapStruct使用合理的默认值,但在配置或实现特殊行为时不加理会。

与动态映射框架相比,MapStruct具有以下优点:

   ①. 通过使用普通方法调用而不是反射来快速执行

   ②. 编译时类型安全性:只能映射彼此映射的对象和属性,不能将订单实体意外映射到客户DTO等。

   ③.  如果生成,清除错误报告

二、项目构建

  使用开发工具IDEA创建SpringBoot项目,添加pom依赖如下:

<modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.thinkingcao</groupId>
    <artifactId>springboot-mapstruct</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-mapstruct</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-jdk8</artifactId>
            <version>1.2.0.Final</version>
        </dependency>

        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>1.2.0.Final</version>
            <scope>provided</scope>
        </dependency>
        <!-- other dependencies -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.36</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

 

三、阅读官方文档

 官方文档:https://mapstruct.org/documentation/stable/reference/html/

 文档版本:MapStruct 1.3.1最终参考指南

四、项目配置

 1. 由于本次需要从数据库读取数据展示,为了方便,我们使用JPA,所以,这里配置一下JPA的数据库连接属性

### 开发环境
## 端口配置
server:
  port: 8080
spring:
  datasource: ##数据库配置
    url: jdbc:mysql://localhost:3306/springboot-mapstruct?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: 123456
  jpa:
    database: MYSQL  #数据库类型
    show-sql: true   #打印SQL语句
    hibernate:
      ddl-auto: update
    properties:
      hibernate:
      dialect: org.hibernate.dialect.MySQL5Dialect

五、项目配置准备工作

 我们采用订单(order)和商品(good)为例子,Po分别为订单表和商品表,订单表中的商品id与商品表中的商品id相关联,因此实体类Po分别为Good和Order,返回前端的实体类传输对象Vo为GoodsOrderVo

1. Good(商品)表 如下:

package com.thinkingcao.springbootmapstruct.entity;

import lombok.Data;

import javax.persistence.*;

/**
 * <pre>
 * @desc: 商品表 
 * @author: cao_wencao
 * @date: 2019-11-04 22:36
 * @version: 1.0
 * </pre>
 */
@Entity
@Table(name="t_good")
@Data
public class Good {
    @Id
    @Column(name ="good_id",columnDefinition = "int(100) COMMENT '商品编号id'")
    @GeneratedValue(strategy = GenerationType.AUTO)       //主键自动增长
    private int goodId;
    
    @Column(name = "good_type",columnDefinition = "varchar(100) COMMENT '商品类别'")
    private String goodType;

    @Column(name = "good_name",columnDefinition = "varchar(100) COMMENT '商品名称'")
    private String goodName;

    @Column(name = "good_num",columnDefinition = "int(100) COMMENT '商品数量'")
    private int goodNum;

    @Column(name = "good_price",columnDefinition = "int(100) COMMENT '商品价格'")
    private int goodPrice;
}

2. Order(订单)表 如下:

package com.thinkingcao.springbootmapstruct.entity;

import lombok.Data;

import javax.persistence.*;

/**
 * <pre>
 * @desc:订单表 d
 * @author: cao_wencao
 * @date: 2019-11-04 22:36
 * @version: 1.0
 * </pre>
 */
@Entity
@Table(name="t_order")
@Data
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "order_id" ,columnDefinition = "int(100) COMMENT '订单编号id'")
    
    private int orderId;
    @Column(name = "good_id",columnDefinition = "int(100) COMMENT '商品编号id'")
    private int goodId;

    @Column(name = "order_money" ,columnDefinition = "int(100) COMMENT '订单金额'")
    private double orderMoney;

    @Column(name = "receiver_address",columnDefinition = "varchar(255) COMMENT '收货地址'")
    private String receiverAddress;

    @Column(name = "receiver_name",columnDefinition = "varchar(100) COMMENT '收货姓名'")
    private String receiverName;

    @Column(name = "receiver_phone",columnDefinition = "varchar(100) COMMENT '手机号'")
    private String receiverPhone;

    @Column(name = "pay_state",columnDefinition = "int(100) COMMENT '支付状态'")
    private int payState;

  

   

}

3.创建GoodsRepository

package com.thinkingcao.springbootmapstruct.mapper;

import com.thinkingcao.springbootmapstruct.entity.Good;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * <pre>
 * @desc: GoodsRepository
 * @author: cao_wencao
 * @date: 2019-11-04 22:58
 * @version: 1.0
 * </pre>
 */
public interface GoodsRepository extends JpaRepository<Good,Integer> {
}

4.创建OrdersRepository

package com.thinkingcao.springbootmapstruct.mapper;

import com.thinkingcao.springbootmapstruct.entity.Order;
import org.springframework.data.jpa.repository.JpaRepository;


/**
 * <pre>
 * @desc:  OrdersRepository
 * @author: cao_wencao
 * @date: 2019-11-04 22:58
 * @version: 1.0
 * </pre>
 */
public interface OrdersRepository extends JpaRepository<Order,Integer> {
}

5.创建GoodsOrderVo

package com.thinkingcao.springbootmapstruct.vo;

import lombok.Data;

/**
 * <pre>
 * @desc: 商品订单(DTO数据传输对象)
 * @author: cao_wencao
 * @date: 2019-11-04 23:05
 * @version: 1.0
 * </pre>
 */
@Data
public class GoodsOrderVo {

    private int orderId;    //订单编号ID

    private int orderMoney; //订单金额

    private int payState; //支付状态

    private String goodType; //商品类别

    private String goodName; //商品名称
}

   从GoodOrderVo这个类可以看出,GoodOrderVo集成了商品表和订单表,在查询时,我们需要使用MapStruct自动映射到GoodOrderVo,然后查询结果需要返回以上字段;

五、配置MapStruct

前面第五部分准备工作做的差不多了,关键步骤在于配置MapStruct这步,我们的最终目的是为了自定义一个Vo实体类,用于返回数据到前端,用于前端展示,Vo类如下:

package com.thinkingcao.springbootmapstruct.inter;

import com.thinkingcao.springbootmapstruct.vo.GoodsOrderVo;
import com.thinkingcao.springbootmapstruct.entity.Good;
import com.thinkingcao.springbootmapstruct.entity.Order;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;

@Mapper(componentModel = "spring")
public interface GoodOrderMapper {
    @Mappings({
            @Mapping(source = "order.orderId", target = "orderId"),
            @Mapping(source = "order.orderMoney", target = "orderMoney"),
            @Mapping(source = "order.payState", target = "payState"),
            @Mapping(source = "good.goodType", target = "goodType"),
            @Mapping(source = "good.goodName",target = "goodName")
    })
    GoodsOrderVo fromGoodOrderDTO(Good good, Order order);
}
  • @Mapper注解标记这个接口作为一个映射接口,并且是编译时MapStruct处理器的入口。
  • @Mapping解决源对象和目标对象中,属性名字不同的情况

五、编写Controller

package com.thinkingcao.springbootmapstruct.controller;

import com.thinkingcao.springbootmapstruct.vo.GoodsOrderVo;
import com.thinkingcao.springbootmapstruct.entity.Good;
import com.thinkingcao.springbootmapstruct.entity.Order;
import com.thinkingcao.springbootmapstruct.inter.GoodOrderMapper;
import com.thinkingcao.springbootmapstruct.mapper.GoodsRepository;
import com.thinkingcao.springbootmapstruct.mapper.OrdersRepository;
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.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.Optional;

/**
 * <pre>
 * @desc: 商品-订单Controller
 * @author: cao_wencao
 * @date: 2019-11-04 23:16
 * @version: 1.0
 * </pre>
 */
@RestController
@RequestMapping("/goodOrder")
public class GoodOrderController {
    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private OrdersRepository ordersRepository;

    @Autowired
    private GoodOrderMapper goodOrderMapper;

    @RequestMapping(value = "/detail/{goodId}", method = RequestMethod.GET)
    public GoodsOrderVo details(@PathVariable(value = "goodId") Integer goodId) {

        Good goodInfo = goodsRepository.findById(goodId).get();

        Optional<Order> optionalT = ordersRepository.findById(goodId);

        Order order = optionalT.isPresent() ? optionalT.get() : null;
        
        return goodOrderMapper.fromGoodOrderDTO(goodInfo, order);
    }
}

六、运行主程序SpringBootMapStructApplication.java

注:第一次运行JPA会自动创建数据库表,控制台会打印创建表SQL语句

七、测试

1、给数据表插入测试数据

t_good表:  INSERT INTO `t_good` VALUES ('111111', '面包', '100', '5', '零食');

t_order表: INSERT INTO `t_order` VALUES ('1', '111111', '10', '1', '上海市徐汇区腾讯大厦', '曹', '13028193378');

2、访问http://127.0.0.1:8080/goodOrder/detail/111111   

显示结果如下:

八、MapStruct与案例分析

  从前面使用MapStruct进行对象属性复制来看 , 作为一个注解处理器, 通过MapStruct生成的代码具有怎么样的优势呢?

  

 

 对应的代码:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.thinkingcao.springbootmapstruct.inter;

import com.thinkingcao.springbootmapstruct.entity.Good;
import com.thinkingcao.springbootmapstruct.entity.Order;
import com.thinkingcao.springbootmapstruct.vo.GoodsOrderVo;
import org.springframework.stereotype.Component;

@Component
public class GoodOrderMapperImpl implements GoodOrderMapper {
    public GoodOrderMapperImpl() {
    }

    public GoodsOrderVo fromGoodOrderDTO(Good good, Order order) {
        if (good == null && order == null) {
            return null;
        } else {
            GoodsOrderVo goodsOrderVo = new GoodsOrderVo();
            if (good != null) {
                goodsOrderVo.setGoodType(good.getGoodType());
                goodsOrderVo.setGoodName(good.getGoodName());
            }

            if (order != null) {
                goodsOrderVo.setPayState(order.getPayState());
                goodsOrderVo.setOrderMoney((int)order.getOrderMoney());
                goodsOrderVo.setOrderId(order.getOrderId());
            }

            return goodsOrderVo;
        }
    }
}

 可以看到其生成了一个实现类, 而代码也类似于我们手写, 通俗易懂,并且在生成的代码中, 我们可以轻易的进行 debug。在使用反射的时候, 如果出现了问题, 很多时候是很难找到是什么原因的。

8.1 高性能

 这是相对反射来说的, 反射需要去读取字节码的内容, 花销会比较大。 而通过 MapStruct来生成的代码, 其类似于人手写。 速度上可以得到保证。前面例子中生成的代码可以在编译后看到。 在 target/generated-sources/annotations 里可以看到。

九、项目完整结构

十、源码

GitHub源码:https://github.com/Thinkingcao/SpringBootLearning/tree/master/springboot-mapstruct

 

文章最后发布于: 2019-11-05 01:05:33
展开阅读全文
0 个人打赏
私信求帮助

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览