mapstruct

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

MapStruct Object Mapping

MapStruct 对象映射

Deep Knowledge: Use
mcp__documentation__fetch_docs
with technology:
mapstruct
for comprehensive documentation.
深度知识:使用
mcp__documentation__fetch_docs
工具,指定technology为
mapstruct
以获取完整文档。

Basic Mapper

基础映射器

java
@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);
}
java
@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);
}

Update Mapping

更新映射

java
@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);
}
java
@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);
}

Field Mapping

字段映射

java
@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);
}
java
@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);
}

Nested Objects

嵌套对象

java
@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);
}
java
@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);
}

Enum Mapping

枚举映射

java
@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);
}
java
@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);
}

Custom Methods

自定义方法

java
@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());
    }
}
java
@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());
    }
}

Collection Mapping

集合映射

java
@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);
    }
}
java
@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);
    }
}

After/Before Mapping

映射前后处理

java
@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());
        }
    }
}
java
@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());
        }
    }
}

Conditional Mapping

条件映射

java
@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);
}
java
@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 Configuration

Maven 配置

xml
<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>
                    <!-- Order matters: Lombok first -->
                    <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>
xml
<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>
                    <!-- Order matters: Lombok first -->
                    <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>

Key Annotations

核心注解

AnnotationPurpose
@Mapper
Define mapper interface/abstract class
@Mapping
Field-level mapping configuration
@MappingTarget
Update existing object
@BeanMapping
Bean-level mapping settings
@AfterMapping
Post-processing method
@BeforeMapping
Pre-processing method
@Condition
Conditional mapping
@ValueMapping
Enum value mapping
注解用途
@Mapper
定义映射器接口/抽象类
@Mapping
字段级映射配置
@MappingTarget
更新已有对象
@BeanMapping
类级映射设置
@AfterMapping
映射后处理方法
@BeforeMapping
映射前处理方法
@Condition
条件映射
@ValueMapping
枚举值映射

Best Practices

最佳实践

  1. Use
    componentModel = "spring"
    for Spring injection
  2. Use
    unmappedTargetPolicy = ReportingPolicy.IGNORE
    for DTOs with fewer fields
  3. Always exclude
    id
    ,
    createdAt
    ,
    updatedAt
    when mapping from DTOs
  4. Use
    @MappingTarget
    for partial updates
  5. Order annotation processors: Lombok → MapStruct → Binding
  6. Use abstract classes instead of interfaces for custom logic
  7. Use
    @Condition
    for conditional mapping logic

  1. 使用
    componentModel = "spring"
    以支持Spring注入
  2. 对于字段较少的DTO,使用
    unmappedTargetPolicy = ReportingPolicy.IGNORE
  3. 从DTO映射到实体时,始终排除
    id
    createdAt
    updatedAt
    字段
  4. 使用
    @MappingTarget
    实现部分更新
  5. 注解处理器顺序:Lombok → MapStruct → Binding
  6. 如需自定义逻辑,使用抽象类而非接口
  7. 使用
    @Condition
    实现条件映射逻辑

When NOT to Use This Skill

不适用场景

ScenarioUse Instead
Java language features
java
skill
Lombok annotations
lombok
skill
Spring configuration
backend-spring-boot
skill
JPA entity operationsJPA-specific skills
Simple copyingManual mapping or BeanUtils

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

Anti-Patterns

反模式

Anti-PatternWhy It's BadCorrect Approach
Mapping entities to entitiesBreaks change trackingMap DTO to entity
Not using @MappingTargetInefficient updatesUse for partial updates
Complex logic in expressionsHard to testUse custom methods
Ignoring all unmappedMisses fields silentlyUse WARN or ERROR policy
Not excluding audit fieldsOverwrites metadataExclude id, timestamps
Circular referencesStackOverflowErrorBreak cycles with custom mapping
Using interfaces for custom logicCan't inject dependenciesUse abstract classes
Not testing mappersRuntime mapping errorsWrite mapper tests

反模式问题所在正确做法
实体到实体的映射破坏变更追踪从DTO映射到实体
未使用@MappingTarget更新效率低下使用该注解实现部分更新
在表达式中编写复杂逻辑难以测试使用自定义方法
忽略所有未映射字段静默丢失字段使用WARN或ERROR策略
未排除审计字段覆盖元数据排除id、时间戳字段
循环引用导致StackOverflowError通过自定义映射打破循环
使用接口实现自定义逻辑无法注入依赖使用抽象类
未测试映射器运行时出现映射错误编写映射器测试用例

Quick Troubleshooting

快速排查

IssueCauseSolution
"Cannot find implementation"Annotation processing failedCheck processor configuration
Lombok fields not foundWrong processor orderLombok before MapStruct
Mapper not autowiredWrong componentModelUse componentModel = "spring"
Circular dependencyMappers reference each otherUse @Lazy or refactor
UnmappedTargetProperty warningMissing mappingAdd @Mapping or ignore policy
NullPointerException in mappingNull sourceAdd null checks or NullValuePropertyMappingStrategy
Custom method not calledWrong signatureMatch method parameters exactly
Generated code not updatedIDE cacheClean and rebuild project

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

Reference Documentation

参考文档

Deep Knowledge: Use
mcp__documentation__fetch_docs
with technology:
mapstruct
for comprehensive documentation.
深度知识:使用
mcp__documentation__fetch_docs
工具,指定technology为
mapstruct
以获取完整文档。