MapStruct 简单介绍及结合 SpringBoot 使用

15,141 阅读3分钟

MapStruct

1 什么是 MapStruct?

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

2 为什么使用 MapStruct?

方案优点缺点
手写代码1. 灵活性高
2. 方便后续重构
1. 重复性工作多
2. 手写代码容易遗漏掉有些字段
BeanUtils.copyProperties 使用反射实现1. 使用简单
2. Apache 的包效率比较低,spring 的包效率可以接受
1. 复杂场景支持不足,控制 copy 粒度太粗
2. 不易重构
MapStruct1. 灵活性高支持简单,复杂,嵌套,自定义扩展等多种手段
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/…