MapStruct简单介绍及结合SpringBoot使

MapStruct

1 什么是 MapStruct?

MapStruct 是一个代码生成器,主要用于 Java Bean 之间的映射,如 entity 到 DTO 的映射。

2 为什么使用 MapStruct?

方案 优点 缺点
手写代码 1. 灵活性高
2. 方便后续重构
1. 重复性工作多
2. 手写代码容易遗漏掉有些字段
BeanUtils.copyProperties 使用反射实现 1. 使用简单
2. Apache 的包效率比较低,spring 的包效率可以接受
1. 复杂场景支持不足,控制 copy 粒度太粗
2. 不易重构
MapStruct 1. 灵活性高支持简单,复杂,嵌套,自定义扩展等多种手段
2. 编译期生成,没有效率问题
3. 不方便后续重构

3 结合 SpringBoot 实现

3.1 导入 Maven 依赖以及插件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.12.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>cn.muzaijian.mall</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.0-RELEASE</version>
    <name>mapstruct</name>
    <description>Map Struct project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
        <mapstruct.version>1.4.1.Final</mapstruct.version>
        <lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!-- SpringBoot Test -->
        <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>
        <!-- MapStruct domain 映射工具 -->
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>${mapstruct.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- Spring Boot 插件,可以把应用打包为可执行 Jar -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <!-- Maven 编译插件,提供给 MapStruct 使用 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven-compiler-plugin.version}</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <annotationProcessorPaths>
                        <!-- MapStruct 注解处理器 -->
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>${mapstruct.version}</version>
                        </path>
                        <!-- Lombok 注解处理器 -->
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>${lombok.version}</version>
                        </path>
                        <!-- MapStruct 和 Lombok 注解绑定处理器 -->
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok-mapstruct-binding</artifactId>
                            <version>${lombok-mapstruct-binding.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>
复制代码
  • 如未使用 lombok 可以去除后两个注解处理器

3.2 编写 entity

3.2.1 PmsBrand

package cn.muzaijian.mall.mapstruct.mbg.entity;

import lombok.Data;
import lombok.EqualsAndHashCode;

import java.io.Serializable;

/**
 * <p>
 * 商品品牌
 * </p>
 *
 * @author muzaijian
 * @since 2021-07-12
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class PmsBrand implements Serializable {

    private Long id;

    /**
     * 名称
     */
    private String name;

    /**
     * 首字母
     */
    private String firstLetter;

    /**
     * 排序
     */
    private Integer sort;

    /**
     * 是否为制造商品牌:0->不是;1->是
     */
    private Integer factoryStatus;

    /**
     * 是否进行显示:0->不显示;1->显示
     */
    private Integer showStatus;

    /**
     * 商品数量
     */
    private Integer productCount;

    /**
     * 商品评论数量
     */
    private Integer productCommentCount;

    /**
     * 品牌 logo
     */
    private String logo;

    /**
     * 专区大图
     */
    private String bigPic;

    /**
     * 品牌故事
     */
    private String brandStory;

}
复制代码

3.2.2 PmsBrandItemDTO

package cn.muzaijian.mall.mapstruct.domain.dto;

import lombok.Data;
import lombok.EqualsAndHashCode;

import java.io.Serializable;

/**
 * <p>
 * 商品品牌 ItemDTO
 * </p>
 *
 * @author muzaijian
 * @date 2021/2/26
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class PmsBrandItemDTO implements Serializable {

    private String name;

    private String firstLetter;

    private Integer sort;

    private Integer factoryStatus;

    private Integer showStatus;

    private String logo;

    private String bigPicture;

    private String brandStory;
}
复制代码
  • 可以看到 PmsBrandItemDTO 比 PmsBrand 少了 id 实例变量,并且把 bigPic 修改成了 bigPicture

3.3 编写 MapStruct 转换接口

3.3.1 PmsBrandConvertDemoOne

package cn.muzaijian.mall.mapstruct.convert;

import cn.muzaijian.mall.mapstruct.domain.dto.PmsBrandItemDTO;
import cn.muzaijian.mall.mapstruct.mbg.entity.PmsBrand;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

/**
 * <p>
 * 商品品牌 转换 Demo One
 * </p>
 *
 * @author muzaijian
 * @date 2021/7/12
 */
@Mapper(componentModel = "spring")
public interface PmsBrandConvertDemoOne {

    /**
     * 商品品牌 ItemDTO 转换商品品牌 entity
     *
     * @param brandItemDTO 商品品牌 ItemDTO
     * @return 商品品牌 entity
     */
    @Mapping(source = "bigPicture", target = "bigPic")
    PmsBrand convert(PmsBrandItemDTO brandItemDTO);

}
复制代码
  1. 需结合 Spring 使用
  2. domain 实例变量不一样可以使用 @Mapping 注解指定实例变量映射名称

3.3.2 PmsBrandConvertDemoTwo

package cn.muzaijian.mall.mapstruct.convert;

import cn.muzaijian.mall.mapstruct.domain.dto.PmsBrandItemDTO;
import cn.muzaijian.mall.mapstruct.mbg.entity.PmsBrand;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

/**
 * <p>
 * 商品品牌 转换 Demo Two
 * </p>
 *
 * @author muzaijian
 * @date 2021/7/13
 */
@Mapper
public interface PmsBrandConvertDemoTwo {

    PmsBrandConvertDemoTwo INSTANCE = Mappers.getMapper(PmsBrandConvertDemoTwo.class);

    /**
     * 商品品牌 ItemDTO 转换商品品牌 entity
     *
     * @param brandItemDTO 商品品牌 ItemDTO
     * @return 商品品牌 entity
     */
    @Mapping(source = "bigPicture", target = "bigPic")
    PmsBrand convert(PmsBrandItemDTO brandItemDTO);
}
复制代码

3.4 查看编译后的实现类

3.4.1 PmsBrandConvertDemoOneImpl

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

package cn.muzaijian.mall.mapstruct.convert;

import cn.muzaijian.mall.mapstruct.domain.dto.PmsBrandItemDTO;
import cn.muzaijian.mall.mapstruct.mbg.entity.PmsBrand;
import org.springframework.stereotype.Component;

@Component
public class PmsBrandConvertDemoOneImpl implements PmsBrandConvertDemoOne {
    public PmsBrandConvertDemoOneImpl() {
    }

    public PmsBrand convert(PmsBrandItemDTO brandItemDTO) {
        if (brandItemDTO == null) {
            return null;
        } else {
            PmsBrand pmsBrand = new PmsBrand();
            pmsBrand.setBigPic(brandItemDTO.getBigPicture());
            pmsBrand.setName(brandItemDTO.getName());
            pmsBrand.setFirstLetter(brandItemDTO.getFirstLetter());
            pmsBrand.setSort(brandItemDTO.getSort());
            pmsBrand.setFactoryStatus(brandItemDTO.getFactoryStatus());
            pmsBrand.setShowStatus(brandItemDTO.getShowStatus());
            pmsBrand.setLogo(brandItemDTO.getLogo());
            pmsBrand.setBrandStory(brandItemDTO.getBrandStory());
            return pmsBrand;
        }
    }
}
复制代码

3.4.2 PmsBrandConvertDemoTwoImpl

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

package cn.muzaijian.mall.mapstruct.convert;

import cn.muzaijian.mall.mapstruct.domain.dto.PmsBrandItemDTO;
import cn.muzaijian.mall.mapstruct.mbg.entity.PmsBrand;

public class PmsBrandConvertDemoTwoImpl implements PmsBrandConvertDemoTwo {
    public PmsBrandConvertDemoTwoImpl() {
    }

    public PmsBrand convert(PmsBrandItemDTO brandItemDTO) {
        if (brandItemDTO == null) {
            return null;
        } else {
            PmsBrand pmsBrand = new PmsBrand();
            pmsBrand.setBigPic(brandItemDTO.getBigPicture());
            pmsBrand.setName(brandItemDTO.getName());
            pmsBrand.setFirstLetter(brandItemDTO.getFirstLetter());
            pmsBrand.setSort(brandItemDTO.getSort());
            pmsBrand.setFactoryStatus(brandItemDTO.getFactoryStatus());
            pmsBrand.setShowStatus(brandItemDTO.getShowStatus());
            pmsBrand.setLogo(brandItemDTO.getLogo());
            pmsBrand.setBrandStory(brandItemDTO.getBrandStory());
            return pmsBrand;
        }
    }
}
复制代码
  • 可见两者区别只是 PmsBrandConvertDemoOneImpl 多了一个 @Component 注解,可以直接使用依赖注入方式调用

3.5 测试

package cn.muzaijian.mall.mapstruct;

import cn.muzaijian.mall.mapstruct.convert.PmsBrandConvertDemoOne;
import cn.muzaijian.mall.mapstruct.convert.PmsBrandConvertDemoTwo;
import cn.muzaijian.mall.mapstruct.domain.dto.PmsBrandItemDTO;
import cn.muzaijian.mall.mapstruct.mbg.entity.PmsBrand;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class MapstructApplicationTests {

    @Autowired
    private PmsBrandConvertDemoOne brandConvertDemoOne;

    @Test
    void testMapStructDemoOne() {
        PmsBrandItemDTO brandItemDTO = new PmsBrandItemDTO();
        brandItemDTO.setBigPicture("picture");
        brandItemDTO.setBrandStory("阿里云故事");
        brandItemDTO.setLogo("阿里云 logo");
        PmsBrand brand = brandConvertDemoOne.convert(brandItemDTO);
        System.out.println(brand);
    }

    @Test
    void testMapStructDemoTwo() {
        PmsBrandItemDTO brandItemDTO = new PmsBrandItemDTO();
        brandItemDTO.setBigPicture("picture");
        brandItemDTO.setBrandStory("腾讯云故事");
        brandItemDTO.setLogo("腾讯云 logo");
        PmsBrand brand = PmsBrandConvertDemoTwo.INSTANCE.convert(brandItemDTO);
        System.out.println(brand);
    }

}
复制代码
  • DemoOne 使用注入的方式调用,DemoTwo 使用接口中生成的成员变量 INSTANCE 调用

4 项目源码地址

github.com/a616766585/…