MapStruct(对象转换)
# MapStruct
# 一、前言与背景
随着项目逐渐成熟,模块化设计越来越细致。通常,实体类会存放在 domain
模块中,但为了减少耦合,其他模块不应直接依赖 domain
模块。这时,各自模块需要创建相应的 model
类,并根据实际业务需要从 domain
中的实体类映射数据。传统的手动映射既繁琐又容易出错,于是 MapStruct 就派上了用场。
# 二、为什么选择 MapStruct 而不是 BeanUtils
?
虽然 Spring 的 BeanUtils.copyProperties()
也能实现简单的对象映射,但它存在以下缺点:
- 只能映射相同属性名的字段:如果属性名不一致,映射就会失败。
- 数据类型不一致时的映射:
BeanUtils
不支持自动类型转换,如从String
转换为LocalDateTime
。 - 性能问题:
BeanUtils
基于反射实现,性能较低。而 MapStruct 是在编译时生成代码,性能极高。
MapStruct 是一个灵活且性能出色的解决方案,能够处理复杂的映射需求,比如类型转换、字段名不一致等。
# 三、准备工作
# 3.1 添加 Maven 依赖
在项目的 pom.xml
中,需要添加 MapStruct 和 Lombok 相关依赖。如果你使用了 Lombok 来简化 POJO(Plain Old Java Object)的开发,那么在引入 MapStruct 时,需要确保两者的注解处理器可以正常共存,否则会产生冲突。
基本依赖配置:
<properties>
<!-- 配置 Maven 编译插件使用的 Java 版本 -->
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- 定义 MapStruct 和 Lombok 的版本号 -->
<mapstruct.version>1.5.5.Final</mapstruct.version>
<lombok.version>1.18.16</lombok.version>
</properties>
<dependencies>
<!-- MapStruct 核心库,包含注解如 @Mapper, @Mapping 等 -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<!-- MapStruct 的注解处理器,用于生成映射器实现类 -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<!-- Lombok 库,简化 JavaBean 开发 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
</dependencies>
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
这里引入了 MapStruct 和 Lombok 两个核心依赖:
- MapStruct: 提供对象映射的核心功能。
mapstruct
依赖包含了基础注解和 API,mapstruct-processor
是注解处理器,用于生成实际的映射代码。 - Lombok: 用于生成 Java 类中的常见方法(如 Getter、Setter、Constructor 等),大大减少了样板代码。
# 3.2 解决 Lombok 与 MapStruct 兼容性问题
当 MapStruct 和 Lombok 一起使用时,可能会出现 Lombok 的注解失效的问题。这是因为 MapStruct 的注解处理器优先执行,导致 Lombok 的注解处理器被忽略。例如,你可能会发现 Lombok 的 @Getter
、@Setter
等注解在生成代码时没有效果。
问题分析:
在 Maven 编译过程中,注解处理器的执行顺序非常重要。默认情况下,Maven 会优先执行 MapStruct 的注解处理器,这会导致 Lombok 的注解(如 @Data
, @Builder
)在编译过程中失效,最终生成的代码中缺少 Getter 和 Setter 方法。这是因为 MapStruct 的处理器在生成映射代码时,无法找到由 Lombok 生成的这些方法。
解决方案:
为了避免这种冲突,需要在 maven-compiler-plugin
中手动指定注解处理器的顺序。具体做法是将 Lombok 的注解处理器优先配置,确保 Lombok 先生成必要的代码,然后再由 MapStruct 进行映射代码的生成。还需要引入一个特殊的依赖 lombok-mapstruct-binding
,确保 Lombok 和 MapStruct 的绑定工作顺利进行。
插件配置:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<!-- Lombok 注解处理器,确保优先执行 -->
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<!-- MapStruct 注解处理器,生成映射器实现类 -->
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
<!-- 解决 Lombok 与 MapStruct 兼容问题 -->
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
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
配置解释:
lombok
: 这是 Lombok 的注解处理器,优先执行,确保在生成映射代码前,Lombok 已生成所需的 Getter、Setter、Builder 等方法。mapstruct-processor
: MapStruct 的注解处理器,负责生成映射器的实现类。lombok-mapstruct-binding
: 这是一个桥接库,用于确保 Lombok 和 MapStruct 可以协同工作,避免注解处理器之间的冲突。
# 3.3 理解 @Mapper
注解
@Mapper
是 MapStruct 的核心注解,用于标识一个接口为映射器,并指定相关配置。该注解的配置直接影响生成的映射器实现类的使用方式和集成方式。
componentModel
属性用于指定生成的映射器实现类的组件类型,它支持以下几种模式:
default
: 默认模式,不使用任何组件框架。生成的映射器类是一个普通的 Java 类,你可以通过Mappers.getMapper(Class)
静态方法获取实例。spring
: 生成的实现类会被标注@Component
注解,成为 Spring 组件。这意味着你可以通过 Spring 的依赖注入(如@Autowired
)来获取 Mapper 实例。cdi
: 生成的实现类适用于 CDI(Contexts and Dependency Injection),可以通过@Inject
注入。jsr330
: 生成的实现类带有@javax.inject.Named
和@Singleton
注解,适用于 JSR-330 标准。
# 1. 使用 default
模式
如果你选择 default
模式,生成的映射器实现类不会被注册为 Spring 组件或其他框架组件。你需要手动获取 Mapper 实例。
示例代码:
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
// 将 User 实体映射为 UserDTO
UserDTO toUserDTO(User user);
// 将 UserDTO 映射回 User 实体
User fromUserDTO(UserDTO userDTO);
}
// 在使用的地方手动获取实例
public class UserService {
public UserDTO convertUser(User user) {
// 通过 Mappers.getMapper 获取 Mapper 实例
UserMapper userMapper = UserMapper.INSTANCE;
return userMapper.toUserDTO(user);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
特点与应用场景:
- 适用于没有使用 Spring 框架的项目或简单的场景。
- 需要手动通过
Mappers.getMapper(Class)
获取 Mapper 实例。 - 生成的 Mapper 实现类是单例的,但不依赖于任何框架。
# 2. 使用 spring
模式
当 componentModel
设置为 spring
时,生成的映射器实现类会被标注 @Component
注解,成为 Spring 组件。你可以直接通过依赖注入(如 @Autowired
)来获取 Mapper 实例。
示例代码:
@Mapper(componentModel = "spring")
public interface UserMapper {
// 将 User 实体映射为 UserDTO
UserDTO toUserDTO(User user);
// 将 UserDTO 映射回 User 实体
User fromUserDTO(UserDTO userDTO);
}
// 在使用的地方通过 Spring 依赖注入获取实例
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public UserDTO convertUser(User user) {
// 直接使用注入的 userMapper
return userMapper.toUserDTO(user);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
特点与应用场景:
- 适用于使用 Spring 框架的项目。
- 通过 Spring 容器管理 Mapper 实例,自动处理生命周期和依赖。
- 支持更灵活的配置与扩展,例如可以结合 Spring 的 AOP、事务管理、配置管理等。
特性 | default 模式 | spring 模式 |
---|---|---|
实例获取方式 | 通过 Mappers.getMapper(Class) 静态方法 | 通过 Spring 的依赖注入(@Autowired , @Inject 等) |
是否依赖 Spring | 否 | 是,必须在 Spring 环境下运行 |
实例管理 | 生成的实现类为手动管理,通常是单例 | 由 Spring 容器管理,支持多种 Bean 作用域 |
配置与扩展 | 不支持 Spring 的 AOP、事务管理等 | 可以结合 Spring 配置,支持 AOP、事务管理、配置管理等 |
使用场景 | 适用于不依赖 Spring 的独立项目或轻量级应用 | 适用于基于 Spring 的大型项目,自动化管理和更复杂的业务逻辑 |
实际应用中的选择
- 如果你的项目基于 Spring:强烈推荐使用
componentModel = "spring"
,因为这可以让 Mapper 与其他 Spring 组件无缝集成,简化依赖注入、生命周期管理,并且支持 Spring 提供的各种扩展功能。 - 如果你的项目不依赖于 Spring:使用
default
模式是更合适的选择,MapStruct 依然可以高效地处理对象映射,并且你可以通过静态方法直接获取实例。
# 四、基础用法示例
# 4.1 定义实体类与 DTO 类
在实际开发中,我们经常需要将实体类(如数据库模型)转换为传输对象(DTO),以便在不同层次之间传递数据。例如,一个 User
实体类与它的 DTO 对象 UserDTO
:
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
/**
* 用户ID
* 作为用户的唯一标识符。
*/
private Integer id;
/**
* 用户名
* 用户的名称。
*/
private String name;
/**
* 创建时间
* 字符串格式的时间表示,存储创建时间。
*/
private String createTime;
/**
* 更新时间
* LocalDateTime 格式的更新时间。
*/
private LocalDateTime updateTime;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserDTO {
/**
* 用户ID
* 对应 User 实体类中的 id。
*/
private Integer id;
/**
* 用户名
* 对应 User 实体类中的 name。
*/
private String name;
/**
* 创建时间
* 对应 User 实体类中的 createTime。
*/
private String createTime;
/**
* 更新时间
* 对应 User 实体类中的 updateTime。
*/
private LocalDateTime updateTime;
}
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
在这个例子中,User
和 UserDTO
的字段是完全一致的,MapStruct 可以自动完成这些字段的映射,无需手动编写转换逻辑。
# 4.2 定义 Mapper 接口
Mapper
接口用于定义映射规则。MapStruct 通过这个接口生成实际的映射实现类。
@Mapper(componentModel = "spring")
public interface UserMapper {
// 使用 Mappers 工具类获取自动生成的实例对象,适用于不使用 Spring 时。
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
/**
* 将 User 实体映射为 UserDTO。
*
* @param user 原始的 User 实体对象,包含所有原始数据。
* @return 返回映射后的 UserDTO 对象,适用于视图层或传输层的数据。
*/
UserDTO toUserDTO(User user);
/**
* 将 UserDTO 映射回 User 实体。
*
* @param userDTO 原始的 UserDTO 对象,通常来自前端或其他外部系统。
* @return 返回映射回的 User 实体,用于业务逻辑处理或持久化操作。
*/
User fromUserDTO(UserDTO userDTO);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
在这个 UserMapper
接口中,定义了两个映射方法:
toUserDTO(User user)
: 将User
实体对象转换为UserDTO
。fromUserDTO(UserDTO userDTO)
: 将UserDTO
对象转换回User
实体。
生成的实现类会在编译时自动生成,你无需手动编写。
# 4.3 字段数量不一致时的处理
在实际开发中,经常遇到源对象与目标对象字段数量不一致的情况。例如,目标对象只需要部分字段,这时需要特定的映射逻辑。
定义一个字段较少的 VO 类:
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserVO2 {
/**
* 用户ID
* 对应 User 实体类中的 id 字段。
*/
private Integer id;
/**
* 用户名
* 对应 User 实体类中的 name 字段。
*/
private String name;
/**
* 创建时间
* 对应 User 实体类中的 createTime 字段。
*/
private String createTime;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
此时,我们可以定义一个仅处理字段较少映射的接口方法:
@Mapper(componentModel = "spring")
public interface UserMapper {
// 其他转换方法省略...
/**
* 将 User 实体转换为 UserVO2 对象。
* 适用场景:当目标对象的字段少于源对象,通常用于只需要部分数据的场景。
* 注意:由于 UserVO2 字段较少,无法提供反向转换方法。
*
* @param user 源 User 实体对象,包含所有原始数据。
* @return 返回转换后的 UserVO2 对象,适用于只需要部分数据的场景。
*/
UserVO2 toUserVO2(User user);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
注意:由于 UserVO2
中的字段较少,因此不会提供将 UserVO2
转换回 User
的方法。这种情况下的映射是单向的,只能从 User
转换为 UserVO2
。
# 4.4 使用 MapStruct 进行转换
在实际项目中,可以通过 Spring 注入或静态访问获取 Mapper 实例,并进行对象转换:
@RestController
public class UserController {
@GetMapping("/convert")
public List<Object> convertUser() {
// 创建 User 实体,模拟源数据
User user = User.builder()
.id(1) // 设置用户ID
.name("张三") // 设置用户名
.createTime("2024-08-26 10:00:00") // 设置创建时间
.updateTime(LocalDateTime.now()) // 设置当前时间为更新时间
.build();
// 创建一个列表来存储转换结果
List<Object> results = new ArrayList<>();
results.add(user); // 添加原始的 User 对象
// 转换为 UserDTO 对象
UserDTO userDTO = UserMapper.INSTANCE.toUserDTO(user);
results.add("UserDTO: " + userDTO); // 添加转换后的 UserDTO 对象
// 将 UserDTO 转换回 User 对象
User convertedUser = UserMapper.INSTANCE.fromUserDTO(userDTO);
results.add("转换回的 User: " + convertedUser); // 添加转换回的 User 对象
// 转换为字段较少的 UserVO2 对象
UserVO2 userVO2 = UserMapper.INSTANCE.toUserVO2(user);
results.add("UserVO2: " + userVO2); // 添加转换后的 UserVO2 对象
return results; // 返回包含所有转换结果的列表
}
}
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
在这个示例中,我们展示了如何通过 MapStruct 在不同对象之间进行转换,包括从 User
转换为 UserDTO
,以及从 User
转换为字段较少的 UserVO2
。
# 4.5 查看生成的代码
通过反编译生成的代码,可以看到 MapStruct 自动生成的实现类,例如 UserMapperImpl
:
@Component
public class UserMapperImpl implements UserMapper {
@Override
public UserDTO toUserDTO(User user) {
if (user == null) {
return null; // 如果源对象为空,直接返回 null
}
// 构建 UserDTO 对象,并将对应的字段值映射到目标对象中
return UserDTO.builder()
.id(user.getId()) // 映射 id 字段
.name(user.getName()) // 映射 name 字段
.createTime(user.getCreateTime()) // 映射 createTime 字段
.updateTime(user.getUpdateTime()) // 映射 updateTime 字段
.build();
}
@Override
public User fromUserDTO(UserDTO userDTO) {
if (userDTO == null) {
return null; // 如果源对象为空,直接返回 null
}
// 构建 User 实体,并将对应的字段值映射到目标对象中
return User.builder()
.id(userDTO.getId()) // 映射 id 字段
.name(userDTO.getName()) // 映射 name 字段
.createTime(userDTO.getCreateTime()) // 映射 createTime 字段
.updateTime(userDTO.getUpdateTime()) // 映射 updateTime 字段
.build();
}
@Override
public UserVO2 toUserVO2(User user) {
if (user == null) {
return null; // 如果源对象为空,直接返回 null
}
// 构建 UserVO2 对象,并将对应的字段值映射到目标对象中
return UserVO2.builder()
.id(user.getId()) // 映射 id 字段
.name(user.getName()) // 映射 name 字段
.createTime(user.getCreateTime()) // 映射 createTime 字段
.build();
}
}
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
生成的代码完全基于接口定义,所有映射逻辑在编译时生成,无需手动干预。这大大减少了开发中的重复工作量,同时确保了映射逻辑的一致性。
# 五、处理复杂映射场景
MapStruct 不仅可以处理简单的映射,还支持通过注解处理复杂场景,如字段类型不一致、字段名不一致、枚举类型映射等。以下将详细介绍这些场景的处理方法。
# 5.1 字段类型不一致
在实际开发中,不同的业务需求可能导致相同属性在不同对象中使用不同的数据类型。比如,User
实体类中的 createTime
是 String
类型,而在 UserDTO
中是 LocalDateTime
类型。这时我们需要在映射过程中进行类型转换。
代码示例:
@Mapper(componentModel = "spring")
public interface UserMapper {
@Mappings({
// 使用表达式将 String 类型的 createTime 转换为 LocalDateTime 类型
@Mapping(source = "createTime", target = "createTime", expression = "java(com.example.util.DateTransform.strToDate(source.getCreateTime()))")
})
UserDTO toUserDTO(User user);
// 反向转换时,可以选择不处理特定字段或自行定义处理逻辑
User fromUserDTO(UserDTO userDTO);
}
2
3
4
5
6
7
8
9
10
11
12
辅助工具类:
为了完成 String
到 LocalDateTime
的转换,我们定义一个工具类 DateTransform
:
public class DateTransform {
/**
* 将字符串格式的日期转换为 LocalDateTime。
*
* @param str 日期字符串,格式为 "yyyy-MM-dd HH:mm:ss"
* @return 转换后的 LocalDateTime 对象
*/
public static LocalDateTime strToDate(String str) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
return LocalDateTime.parse(str, formatter);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
通过上述表达式配置,MapStruct 能够自动处理类型转换问题。
总结
- 当源字段和目标字段类型不一致时,可以通过
expression
属性自定义转换逻辑。 - 使用工具类方法简化复杂的转换逻辑,使代码更具复用性。
# 5.2 字段名不一致
不同对象的字段名可能不一致,但它们实际上表达的是相同的业务含义。在这种情况下,我们可以通过 @Mapping
注解显式指定字段的映射关系。
代码示例:
假设 User
实体类的 id
和 name
字段在 UserDTO
中被命名为 userId
和 userName
:
@Mapper(componentModel = "spring")
public interface UserMapper {
@Mappings({
// 显式指定字段名映射关系
@Mapping(source = "id", target = "userId"),
@Mapping(source = "name", target = "userName")
})
UserDTO toUserDTO(User user);
// 反向映射时使用相同的配置
User fromUserDTO(UserDTO userDTO);
}
2
3
4
5
6
7
8
9
10
11
12
13
在这个示例中,source
属性指定源对象的字段,target
属性指定目标对象的字段。
总结
- 当源对象和目标对象字段名不一致时,通过
@Mapping
注解明确字段对应关系。 source
和target
属性简化了字段名不一致时的映射工作,避免了手动编写转换代码。
# 5.3 枚举类型映射
处理枚举类型时,MapStruct 也能自动完成映射,尤其是在枚举与字符串之间的转换。例如,UserType
枚举在 User
中存储为枚举类型,而在 UserDTO
中则存储为字符串。
# 枚举定义
@Getter
@AllArgsConstructor
public enum UserType {
JAVA("001", "Java Developer"),
DB("002", "Database Admin"),
LINUX("003", "Linux Admin");
private final String code; // 枚举代码
private final String description; // 枚举描述
}
2
3
4
5
6
7
8
9
10
# 实体类与 DTO 定义
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserEnum {
private Integer id;
private String name;
private UserType userType; // 枚举类型字段
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserEnumDTO {
private Integer id;
private String name;
private String type; // 字符串类型字段
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Mapper 定义
在映射过程中,MapStruct 可以自动调用枚举的 toString()
方法或使用自定义逻辑:
@Mapper(componentModel = "spring")
public interface UserEnumMapper {
// 将枚举类型映射为字符串
@Mapping(source = "userType", target = "type")
UserEnumDTO toUserEnumDTO(UserEnum userEnum);
// 反向映射:从字符串映射回枚举
@Mapping(source = "type", target = "userType")
UserEnum fromUserEnumDTO(UserEnumDTO userEnumDTO);
}
2
3
4
5
6
7
8
9
10
11
总结
- 当枚举类型与字符串类型进行映射时,MapStruct 可以自动完成转换。
- 如果枚举类型的映射需要特殊处理,可以通过自定义方法或表达式进行配置。
# 六、MapStruct 的高级用法
MapStruct 不仅可以处理简单对象之间的映射,还支持集合类型的映射、自定义方法的实现等更高级的场景。以下详细介绍如何在这些高级用法中应用 MapStruct。
# 6.1 处理集合类型映射
在实际项目中,我们经常需要将对象的集合进行映射,例如将 List<User>
转换为 List<UserDTO>
。MapStruct 提供了简单的方法来实现集合类型的映射,而不需要手动遍历集合。
代码示例:
@Mapper(componentModel = "spring")
public interface UserMapper {
/**
* 将 List<User> 映射为 List<UserDTO>。
* 适用场景:批量转换对象集合,常用于服务层和控制器层之间的数据传输。
*
* @param users 源 List<User> 集合
* @return 返回映射后的 List<UserDTO> 集合
*/
List<UserDTO> toUserDTOList(List<User> users);
/**
* 将 List<UserDTO> 映射回 List<User>。
* 适用场景:从传输对象集合转换回实体对象集合,常用于持久化数据。
*
* @param userDTOs 源 List<UserDTO> 集合
* @return 返回映射后的 List<User> 集合
*/
List<User> fromUserDTOList(List<UserDTO> userDTOs);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
在这个例子中:
toUserDTOList(List<User> users)
:将List<User>
转换为List<UserDTO>
。适用于批量转换的场景,例如从数据库中获取多个用户数据后传递给视图层。fromUserDTOList(List<UserDTO> userDTOs)
:将List<UserDTO>
转换回List<User>
,适用于从视图层获取数据后传递给业务层或数据持久化层。
MapStruct 能自动处理集合中的每个元素,无需我们手动编写循环逻辑。
生成的代码:
编译后,MapStruct 自动生成的代码会遍历集合并映射每个元素,例如:
@Override
public List<UserDTO> toUserDTOList(List<User> users) {
if (users == null) {
return null; // 如果集合为空,直接返回 null
}
List<UserDTO> list = new ArrayList<>(users.size());
for (User user : users) {
list.add(toUserDTO(user)); // 调用单个对象的映射方法
}
return list;
}
2
3
4
5
6
7
8
9
10
11
12
13
这种方式不仅简化了代码,还保证了映射逻辑的一致性。
总结
- 集合类型映射在 MapStruct 中非常简单,只需定义方法参数和返回值为集合类型即可。
- 自动生成的代码会遍历集合并调用相应的映射方法,无需手动处理循环逻辑。
# 6.2 自定义方法实现
在某些场景下,自动生成的映射无法满足业务需求,这时可以在 Mapper 中定义默认方法来手动处理特定逻辑。例如,当需要处理复杂的枚举类型转换时,或者根据业务规则自定义映射逻辑。
代码示例:
@Mapper(componentModel = "spring")
public interface UserMapper {
@Mappings({
// 映射字段名不一致的属性
@Mapping(source = "name", target = "userName"),
// 使用自定义表达式处理类型转换
@Mapping(source = "createTime", target = "createTime", expression = "java(com.example.util.DateTransform.strToDate(source.getCreateTime()))")
})
UserDTO toUserDTO(User user);
/**
* 自定义方法:将枚举类型映射为字符串。
* 适用场景:当需要根据业务规则处理复杂的映射逻辑时,例如将枚举转换为特定描述。
*
* @param userType 枚举类型 UserType
* @return 返回枚举的描述信息,如果为空则返回 null
*/
default String mapEnumToString(UserType userType) {
return userType != null ? userType.getDescription() : null;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
在这个例子中:
@Mapping(source = "name", target = "userName")
:映射字段名不一致的属性。@Mapping(source = "createTime", target = "createTime", expression = "java(com.example.util.DateTransform.strToDate(source.getCreateTime()))")
:通过表达式处理类型转换,如将String
转换为LocalDateTime
。default String mapEnumToString(UserType userType)
:自定义方法用于将枚举类型转换为字符串。通过default
关键字定义的方法,MapStruct 会在生成的代码中自动调用。
自定义逻辑实现的场景:
- 复杂类型转换:例如,多个字段组合计算后生成目标值。
- 条件映射:根据业务条件决定是否映射某个字段。
- 特殊数据处理:如将枚举转换为描述、将日期格式化为特定字符串等。
总结:
- 自定义方法允许在映射过程中灵活插入自定义逻辑,满足复杂业务需求。
- 通过
default
关键字,可以在 Mapper 接口中直接定义映射逻辑,提升代码复用性。