mcpField
M
mapstruct

mapstruct

github.com/claude-dev-suite开发4667 次安装2 天前更新
代码仓库

关于 mapstruct

面向 Java 开发者的 MapStruct 映射参考技能,涵盖 DTO 与实体间映射、更新策略及常见问题。还提供 Spring、Lombok 集成和映射实践建议。

主要功能

  1. 01对象与集合映射
  2. 02字段及嵌套对象映射
  3. 03部分与完整更新
  4. 04枚举和条件映射
  5. 05映射前后自定义处理
  6. 06配置与故障排查

适用场景

  1. 01将实体转换为响应 DTO
  2. 02用 DTO 更新现有实体
  3. 03映射不同字段名和嵌套类型

MCP 相关

提及可通过 `mcp__documentation__fetch_docs` 获取 MapStruct 的深入文档。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/claude-dev-suite/claude-dev-suite --skill mapstruct

安装后在 Claude Code 中输入/mapstruct确认已加载。

详细介绍

MapStruct 对象映射

深入了解:使用 technology 为 mapstruct 的 mcp__documentation__fetch_docs 获取完整文档。

基本映射器

@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface UserMapper {

    UserResponse toResponse(User user);

    List<UserResponse> toResponseList(List<User> users);

    @Mapping(target = "id", ignore = true)
    @Mapping(target = "createdAt", ignore = true)
    @Mapping(target = "updatedAt", ignore = true)
    @Mapping(target = "password", ignore = true)
    User toEntity(CreateUserRequest dto);
}

更新映射

@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface UserMapper {

    // Partial update - ignores null values
    @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
    void updateEntity(UpdateUserRequest dto, @MappingTarget User user);

    // Full update - sets all fields including nulls
    void updateEntityFull(UpdateUserRequest dto, @MappingTarget User user);
}

字段映射

@Mapper(componentModel = "spring")
public interface OrderMapper {

    // Different field names
    @Mapping(source = "customer.name", target = "customerName")
    @Mapping(source = "customer.email", target = "customerEmail")
    @Mapping(source = "items", target = "orderItems")
    OrderResponse toResponse(Order order);

    // Constant values
    @Mapping(target = "status", constant = "PENDING")
    @Mapping(target = "createdAt", expression = "java(java.time.LocalDateTime.now())")
    Order toEntity(CreateOrderRequest dto);

    // Expression
    @Mapping(target = "fullName", expression = "java(user.getFirstName() + \" \" + user.getLastName())")
    UserResponse toResponse(User user);

    // Date formatting
    @Mapping(source = "createdAt", target = "createdDate", dateFormat = "yyyy-MM-dd")
    OrderResponse toResponse(Order order);
}

嵌套对象

@Mapper(componentModel = "spring", uses = {AddressMapper.class, ItemMapper.class})
public interface OrderMapper {

    // Uses AddressMapper for address field
    // Uses ItemMapper for items collection
    OrderResponse toResponse(Order order);
}

@Mapper(componentModel = "spring")
public interface AddressMapper {
    AddressResponse toResponse(Address address);
}

@Mapper(componentModel = "spring")
public interface ItemMapper {
    ItemResponse toResponse(Item item);
}

枚举映射

@Mapper(componentModel = "spring")
public interface StatusMapper {

    @ValueMappings({
        @ValueMapping(source = "ACTIVE", target = "ENABLED"),
        @ValueMapping(source = "INACTIVE", target = "DISABLED"),
        @ValueMapping(source = MappingConstants.ANY_REMAINING, target = "UNKNOWN")
    })
    ExternalStatus toExternalStatus(InternalStatus status);
}

自定义方法

@Mapper(componentModel = "spring")
public abstract class UserMapper {

    @Autowired
    protected RoleRepository roleRepository;

    public abstract UserResponse toResponse(User user);

    @Mapping(target = "roles", source = "roleIds")
    public abstract User toEntity(CreateUserRequest dto);

    // Custom mapping method
    protected Set<Role> mapRoles(Set<Long> roleIds) {
        if (roleIds == null) return new HashSet<>();
        return roleIds.stream()
            .map(id -> roleRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Role", "id", id)))
            .collect(Collectors.toSet());
    }
}

集合映射

@Mapper(componentModel = "spring")
public interface ProductMapper {

    ProductResponse toResponse(Product product);

    List<ProductResponse> toResponseList(List<Product> products);

    Set<ProductResponse> toResponseSet(Set<Product> products);

    // Page mapping
    default Page<ProductResponse> toResponsePage(Page<Product> products) {
        return products.map(this::toResponse);
    }
}

映射前/后处理

@Mapper(componentModel = "spring")
public abstract class UserMapper {

    @AfterMapping
    protected void afterMapping(@MappingTarget UserResponse response, User user) {
        response.setDisplayName(user.getFirstName() + " " + user.getLastName().charAt(0) + ".");
    }

    @BeforeMapping
    protected void beforeMapping(CreateUserRequest dto) {
        if (dto.getEmail() != null) {
            dto.setEmail(dto.getEmail().toLowerCase().trim());
        }
    }
}

条件映射

@Mapper(componentModel = "spring")
public abstract class UserMapper {

    @Condition
    public boolean isNotEmpty(String value) {
        return value != null && !value.trim().isEmpty();
    }

    // Only maps non-empty strings
    UserResponse toResponse(User user);
}

Maven 配置

<properties>
    <mapstruct.version>1.6.2</mapstruct.version>
    <lombok.version>1.18.30</lombok.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>${mapstruct.version}</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${lombok.version}</version>
        <optional>true</optional>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.13.0</version>
            <configuration>
                <source>17</source>
                <target>17</target>
                <annotationProcessorPaths>
                    
                    <path>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok</artifactId>
                        <version>${lombok.version}</version>
                    </path>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${mapstruct.version}</version>
                    </path>
                    <path>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok-mapstruct-binding</artifactId>
                        <version>0.2.0</version>
                    </path>
                </annotationProcessorPaths>
                <compilerArgs>
                    <arg>-Amapstruct.defaultComponentModel=spring</arg>
                </compilerArgs>
            </configuration>
        </plugin>
    </plugins>
</build>

关键注解

注解用途
@Mapper定义映射器接口/抽象类
@Mapping配置字段级映射
@MappingTarget更新现有对象
@BeanMapping配置 Bean 级映射
@AfterMapping后处理方法
@BeforeMapping预处理方法
@Condition条件映射
@ValueMapping枚举值映射

最佳实践

  1. 在 Spring 注入中使用 componentModel = "spring"
  2. 对字段较少的 DTO 使用 unmappedTargetPolicy = ReportingPolicy.IGNORE
  3. 从 DTO 映射时,始终排除 id、createdAt、updatedAt
  4. 使用 @MappingTarget 进行部分更新
  5. 按顺序配置注解处理器:Lombok → MapStruct → Binding
  6. 自定义逻辑使用抽象类而不是接口
  7. 使用 @Condition 实现条件映射逻辑

不适用此技能的场景

场景替代方案
Java 语言特性java skill
Lombok 注解lombok skill
Spring 配置backend-spring-boot skill
JPA 实体操作JPA 专用技能
简单复制手动映射或 BeanUtils

反模式

反模式缺点正确做法
实体映射到实体会破坏变更跟踪将 DTO 映射到实体
不使用 @MappingTarget更新效率低用于部分更新
在表达式中编写复杂逻辑难以测试使用自定义方法
忽略所有未映射项会静默遗漏字段使用 WARN 或 ERROR 策略
不排除审计字段会覆盖元数据排除 id 和时间戳
循环引用StackOverflowError使用自定义映射打破循环
使用接口实现自定义逻辑无法注入依赖使用抽象类
不测试映射器运行时映射错误编写映射器测试

快速故障排查

问题原因解决方案
"Cannot find implementation"注解处理失败检查处理器配置
找不到 Lombok 字段处理器顺序错误将 Lombok 放在 MapStruct 之前
映射器未自动装配componentModel 错误使用 componentModel = "spring"
循环依赖映射器相互引用使用 @Lazy 或重构
UnmappedTargetProperty 警告缺少映射添加 @Mapping 或忽略策略
映射时出现 NullPointerException源对象为 null添加空值检查或使用 NullValuePropertyMappingStrategy
未调用自定义方法方法签名错误确保方法参数完全匹配
生成的代码未更新IDE 缓存清理并重新构建项目

参考文档

深入了解:使用 technology 为 mapstruct 的 mcp__documentation__fetch_docs 获取完整文档。

评论

支持 Markdown