什么是MapStruct?
MapStruct 是一个 Java 注解处理器,用于生成类型安全、高性能的对象映射代码。与传统的反射式映射库(如 BeanUtils、ModelMapper)不同,MapStruct 在编译时生成映射代码,不使用反射,因此具有以下优势:
- 编译时类型安全:编译期即可发现映射错误
- 高性能:生成的代码是直接的对象赋值,没有反射开销
- 易于调试:生成的代码可直接查看和调试
- 清晰的映射逻辑:通过注解声明式定义映射规则
项目集成
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
|
<properties>
<mapstruct.version>1.6.3</mapstruct.version>
</properties>
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>21</source>
<target>21</target>
<annotationProcessorPaths>
<!-- Lombok 必须在 MapStruct 之前 -->
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
|
注意:如果使用 Lombok,必须确保 Lombok 在 annotationProcessorPaths 中的顺序早于 MapStruct。
基础使用
定义 Mapper 接口
1
2
3
4
5
6
7
8
9
10
11
|
@Mapper(componentModel = "spring")
public interface ResumeMapper {
@Mapping(target = "contentScore", source = "contentScore", qualifiedByName = "nullToZero")
ResumeAnalysisResponse.ScoreDetail toScoreDetail(ResumeAnalysisEntity entity);
@Named("nullToZero")
default int nullToZero(Integer value) {
return value != null ? value : 0;
}
}
|
Spring 集成
通过设置 componentModel = "spring",MapStruct 会将生成的实现类注册为 Spring Bean,可以直接注入使用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
@Service
@RequiredArgsConstructor
public class ResumeService {
private final ResumeMapper resumeMapper;
public ResumeAnalysisResponse analyzeResume(ResumeAnalysisEntity entity) {
// 使用 Mapper 进行转换
ResumeAnalysisResponse.ScoreDetail scoreDetail =
resumeMapper.toScoreDetail(entity);
return new ResumeAnalysisResponse(
entity.getOverallScore(),
scoreDetail,
entity.getSummary(),
// ... 其他字段
);
}
}
|
常用注解详解
@Mapper
| 属性 |
说明 |
componentModel |
设置为 “spring” 则生成 Spring Bean;设置为 “cdi” 则生成 CDI Bean |
unmappedTargetPolicy |
配置未映射目标字段的处理策略,如 IGNORE(忽略)、WARN(警告)、ERROR(报错) |
nullValuePropertyMappingStrategy |
null 值的映射策略 |
@Mapping
1
2
3
4
5
6
|
@Mapping(
target = "userName", // 目标字段
source = "username", // 源字段
qualifiedByName = "toUpperCase" // 使用自定义转换方法
)
UserDTO toUserDTO(User user);
|
@MappingTarget
用于更新已有对象,而不是创建新对象:
1
2
3
4
5
6
7
|
@Mapper(componentModel = "spring")
public interface UserMapper {
// 将 source 的值更新到 target 中
@MappingTarget
void updateUserFromDto(UserDTO dto, @MappingTarget User entity);
}
|
1
|
userMapper.updateUserFromDto(dto, existingUser);
|
进阶用法
自定义类型转换
1
2
3
4
5
6
7
8
9
10
11
12
|
@Mapper(componentModel = "spring")
public interface OrderMapper {
// 使用 expression 进行复杂转换
@Mapping(target = "orderDate",
expression = "java(java.time.LocalDateTime.now())")
OrderDTO toDTO(Order order);
// 枚举转换
@Mapping(target = "status", source = "status", dateFormat = "yyyy-MM-dd")
Order toEntity(OrderDTO dto);
}
|
集合映射
MapStruct 支持自动集合映射:
1
2
3
4
5
6
7
8
9
|
@Mapper(componentModel = "spring")
public interface ListMapper {
// 自动将 List<User> 转换为 List<UserDTO>
List<UserDTO> userListToDtoList(List<User> users);
// Map 映射
Map<String, UserDTO> mapToDtoMap(Map<String, User> map);
}
|
嵌套 Bean 映射
1
2
3
4
5
6
7
8
|
@Mapper(componentModel = "spring")
public interface StudentMapper {
// 自动处理嵌套的 Address 对象
@Mapping(target = "address.city", source = "addr.city")
@Mapping(target = "address.street", source = "addr.street")
StudentDTO toDTO(Student student);
}
|
多个源对象映射
1
2
3
4
5
6
7
8
|
@Mapper(componentModel = "spring")
public interface OrderMapper {
// 从两个源对象映射到目标对象
@Mapping(target = "customerName", source = "customer.name")
@Mapping(target = "productName", source = "product.name")
OrderDTO toDTO(Customer customer, Product product);
}
|
项目实战案例
在实际项目中,MapStruct 常用于以下场景:
1. Entity 与 DTO 转换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// 实体类 -> DTO
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface ResumeMapper {
@Mapping(target = "contentScore", source = "contentScore", qualifiedByName = "nullToZero")
@Mapping(target = "structureScore", source = "structureScore", qualifiedByName = "nullToZero")
@Mapping(target = "skillMatchScore", source = "skillMatchScore", qualifiedByName = "nullToZero")
@Mapping(target = "expressionScore", source = "expressionScore", qualifiedByName = "nullToZero")
@Mapping(target = "projectScore", source = "projectScore", qualifiedByName = "nullToZero")
ResumeAnalysisResponse.ScoreDetail toScoreDetail(ResumeAnalysisEntity entity);
@Named("nullToZero")
default int nullToZero(Integer value) {
return value != null ? value : 0;
}
}
|
2. 请求/响应对象转换
1
2
3
4
5
6
7
8
9
10
|
@Mapper(componentModel = "spring")
public interface UserMapper {
UserResponse toResponse(User user);
User toEntity(UserCreateRequest request);
@MappingTarget
void updateFromRequest(UserUpdateRequest request, @MappingTarget User user);
}
|
最佳实践
-
始终指定 unmappedTargetPolicy = ReportingPolicy.IGNORE:避免因字段不匹配导致的编译警告
-
使用 @Named 注解自定义转换方法:使代码更清晰
-
避免在 Mapper 中写复杂业务逻辑:Mapper 只做对象映射,业务逻辑应在 Service 层处理
-
合理拆分 Mapper:按模块或功能划分,不要把所有映射写在一个 Mapper 里
-
使用 @MapperConfig 共享配置:多个 Mapper 共用同一配置时使用
1
2
3
4
5
6
|
@MapperConfig(
componentModel = "spring",
unmappedTargetPolicy = ReportingPolicy.IGNORE
)
public interface DefaultMapperConfig {
}
|
性能对比
| 映射方式 |
性能 |
类型安全 |
调试难度 |
| MapStruct |
高 |
编译期检查 |
简单 |
| BeanUtils |
低(反射) |
运行时检查 |
困难 |
| ModelMapper |
低(反射) |
运行时检查 |
困难 |
| 手写转换 |
高 |
编译期检查 |
简单 |
总结
MapStruct 是 Java 项目中进行对象映射的最佳选择,它提供了:
- 编译时类型安全
- 高性能的代码生成
- 清晰的映射规则声明
- 良好的 Spring 集成
通过本指南,你应该能够快速上手 MapStruct,并在项目中优雅地处理 Entity 与 DTO 之间的转换。