SpringBoot整合MapStruct对象转换

前言

在项目中我们经常会进行类与类之间相互的转换,DO转VO、VO转DO等。

关于这类转换工具有很多,我一般使用的是Hutool的Bean转换工具进行转换,spring也自带了转换工具,但是相对来说效率不是很高。因为他们或多或少都是基于反射去创建的,多少对效率有些影响。

性能对比博客:https://blog.csdn.net/u014374173/article/details/121117186

为什么使用MapStruct?

MapStrct在编译的时候会自动生成映射,get、set,基于最原始的赋值方式。

整合

引入maven依赖以及插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
<?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>3.3.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cc.staro</groupId>
<artifactId>spring-mapStruct</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-mapStruct</name>
<description>spring-mapStruct</description>
<properties>
<java.version>17</java.version>
<mapstruct.version>1.5.5.Final</mapstruct.version>
<lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<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>
<!-- 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 注解处理器 未使用lombok可删除-->
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<!-- MapStruct 和 Lombok 注解绑定处理器 未使用lombok可删除-->
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>${lombok-mapstruct-binding.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
  • 用户表

下列几乎大部分都会转换为他,所以给他单独提取出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* <p>
*
* </p>
*
* @Auther: Star
* @Description: 用户表
* @Version: 1.0
*/
@Data
public class UserEntity {
/**
* 主键
*/
private Long id;
/**
* 用户名
*/
private String name;
/**
* 年龄
*/
private Integer age;
/**
* 地址
*/
private String address;
/**
* 密码
*/
private String password;
/**
* 邮箱
*/
private String email;
/**
* 角色
*/
private List<Role> roles;
/**
* 角色为 key 权限值为 value
*/
private Map<String, String> permissions;
}

基本映射

我们会创建一个UserDTO对象与userEntity进行转换。为了方便起见,它们的属性字段都使用相同的名称:

  • UserDTO
1
2
3
4
5
6
7
8
9
10
11
@Data
public class UserDTO {
/**
* 用户名
*/
private String name;
/**
* 密码
*/
private String password;
}
  • Controller

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /**
    * 1、基本映射转换
    * @param userDTO
    * @return
    */
    @GetMapping("/mapStruct1")
    public String demo1(@RequestBody UserDTO userDTO){
    System.out.println(userDTO);
    UserEntity userEntity = UserConvert.INSTANCE.convert(userDTO);
    System.out.println(userEntity);
    return "1、基本映射转换";
    }
  • UserConvert

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Mapper
    public interface UserConvert {
    UserConvert INSTANCE = Mappers.getMapper(UserConvert.class);
    /**
    * 1、简单映射
    * UserDTO 转 userEntity
    * @param userDTO
    * @return
    */
    UserEntity convert(UserDTO userDTO);
    }

不同字段间映射

MapStruct通过@Mapping注解对这类情况提供了支持。

  • Controller

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /**
    * 2、不同字段映射
    * @param userEntity
    * @return
    */
    @GetMapping("/mapStruct2")
    public String demo2(@RequestBody UserEntity userEntity){
    System.out.println(userEntity);
    UserVO userVO = UserConvert.INSTANCE.convert(userEntity);
    System.out.println(userVO);
    return "2、不同字段映射";
    }
  • UserConvert

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    /**
    * 2、不同字段映射
    * userEntity 转 UserVO
    * @param userEntity
    * @return
    */
    @Mappings({
    @Mapping(source = "name", target = "nikeName")
    })
    UserVO convert(UserEntity userEntity);
    /**
    * 2、不同字段映射 两种都可以
    * userEntity 转 UserVO
    * @param userEntity
    * @return
    */

    @Mapping(source = "name", target = "nikeName")
    UserVO convert(UserEntity userEntity);
  • UserVo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Data
public class UserVO {
/**
* 用户名
*/
private String nikeName;
/**
* 年龄
*/
private Integer age;
/**
* 地址
*/
private String address;
/**
* 创建时间
*/
private String createTime;
/**
* 邮箱
*/
private String email;
/**
* 角色
*/
private List<Role> roles;
}

多个源类

@MappingTarget指需要进行合并的目标值

  • controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 3、多数据源和并和转换
*
* @param userDTO
* @return
*/
@GetMapping("/mapStruct3")
public String demo3(@RequestBody UserDTO userDTO) {
UserVO userVO = new UserVO();
System.out.println(userDTO + "--" + userVO);
System.out.println("-------------1转换-----------");
UserEntity userEntity = UserConvert.INSTANCE.convert(userDTO, userVO);
System.out.println(userEntity);
System.out.println("-------------2合并-----------");
System.out.println(userDTO + "--" + userVO);
UserConvert.INSTANCE.convertMerge(userDTO,userVO);
System.out.println("合并后:"+userVO);
return "3、多个源类";
}
  • convert
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 3、多数据源转换
* userDTO,userVO 转 userEntity
*
* @return
*/
UserEntity convert(UserDTO userDTO, UserVO userVO);

/**
* 3、多数据源合并
* userDTO,userVO 转 userEntity
*
* @return
*/
@Mapping(source = "name", target = "nikeName")
@Mapping(source = "password", target = "address")
void convertMerge(UserDTO userDTO,@MappingTarget UserVO userVO);

子对象映射

  • controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* 4、子对象映射
* 一个类中包含了其他对象,同时也需要进行转换 那么就需要两种方式
* 1。方式一:
* 创建一个新的convent类,在需要引进的类上 @Mapper(uses = {转换类.class})
* 2。方式二:
* 在当前页面定义转换接口 同时在转换方法上定义 @Named("转换名称")
* 在转换时,qualifiedByName进行引用,实际上不用qualifiedByName进行引用也是可以的,只是如果我们有什么自定义的转换可以使用该方法
* ,@Mapping(source = "", target = "", qualifiedByName = "转换名称")
*
* @return
*/
@GetMapping("/mapStruct4")
public String demo4() {
UserEntity userEntity = DemoController.mockUserEntity();
System.out.println(userEntity);
UserVO userVO = UserConvert.INSTANCE.convert4(userEntity);
System.out.println(userVO);
System.out.println("---------自定义--------");
System.out.println(userEntity);
UserVO userVO2 = UserConvert.INSTANCE.convert4Custom(userEntity);
System.out.println(userVO2);
return "4、子对象映射";
}

/**
* 模拟数据
*/
private static UserEntity mockUserEntity() {
UserEntity userEntity = new UserEntity();
userEntity.setName("star");
userEntity.setAge(18);
userEntity.setAddress("中国");
userEntity.setEmail("232@qq.com");
userEntity.setPassword("123456");
userEntity.setId(1);
userEntity.setRoles(new ArrayList<>() {{
add(new Role(1, "管理员1", "这是一个管理员1"));
add(new Role(2, "管理2员", "这是一个管理员2"));
}});
return userEntity;
}
  • RoleDTO
1
2
3
4
5
6
@Data
@AllArgsConstructor
public class RoleDTO {
private Integer id;
private String name;
}
  • Role
1
2
3
4
5
6
7
@Data
@AllArgsConstructor
public class Role {
private Integer id;
private String name;
private String description;
}
  • convert
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* 4、子对象映射
* userDTO,userVO 转 userEntity
*
* @return
*/
UserVO convert4(UserEntity userEntity);

/**
* 这里应该单开一个类的,然后在类上 定义@Mapper(uses = {Role.class}) 但是这里为了简化就不加了,可以自己去试一试
* @param role
* @return
*/
RoleDTO convert4(Role role);
/**
* 4、子对象映射
* userDTO,userVO 转 userEntity
*
* @return
*/
@Mapping(source = "userEntity.roles", target = "roles",qualifiedByName ="convert4Custom" )
UserVO convert4Custom(UserEntity userEntity);
/**
* 自定义转换4子对象映射
* @param role
* @return
*/
@Named("convert4Custom")
default RoleDTO convert4Custom(Role role){
return new RoleDTO(role.getId(),role.getName()+"自定义");
}

更新现有实例

具体参考多个源类

使用自定义映射方法

具体参考子对象映射

总结

上述就是一些常用的转换方式,集合等也是一样的转换方式,没有什么需要改变的,这里就不进行举例。

至于其他的可以参考下列文章

参考文章:MapStruct使用指南