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);
}
- 需结合 Spring 使用
- 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 调用