Featured image of post MapStruct使用指南

MapStruct使用指南

什么是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);
}

最佳实践

  1. 始终指定 unmappedTargetPolicy = ReportingPolicy.IGNORE:避免因字段不匹配导致的编译警告

  2. 使用 @Named 注解自定义转换方法:使代码更清晰

  3. 避免在 Mapper 中写复杂业务逻辑:Mapper 只做对象映射,业务逻辑应在 Service 层处理

  4. 合理拆分 Mapper:按模块或功能划分,不要把所有映射写在一个 Mapper 里

  5. 使用 @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 之间的转换。

使用 Hugo 构建
主题 StackJimmy 设计

发布了 29 篇文章 | 共 67213 字