Featured image of post MapStruct完全学习指南

MapStruct完全学习指南

MapStruct完全学习指南

MapStruct完全学习指南:从零到一掌握Java对象映射

📚 目录

  1. MapStruct是什么?
  2. 为什么要使用MapStruct?
  3. 环境搭建与配置
  4. 第一个MapStruct示例
  5. 基本映射用法
  6. 高级映射技巧
  7. 集合与复杂对象映射
  8. 最佳实践与性能优化
  9. 实战案例
  10. 常见问题与解决方案

MapStruct是什么?

MapStruct 是一个基于注解处理器的Java代码生成工具,专门用于简化Java Bean之间的映射转换。它在编译时生成类型安全且高性能的映射代码,大大减少了手工编写模板代码的工作量。

核心特点

  • 🚀 编译时代码生成:无运行时反射,性能优异
  • 🔒 类型安全:编译时检查,避免运行时错误
  • 📝 简洁注解:通过简单注解即可完成复杂映射
  • 🔄 双向映射:支持自动生成反向映射
  • 🎯 Spring集成:完美支持依赖注入框架

为什么要使用MapStruct?

传统映射的痛点

在日常开发中,我们经常需要在不同的对象之间进行数据转换:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 传统手工映射 - 繁琐且容易出错
public UserDTO convertToDTO(User user) {
    UserDTO dto = new UserDTO();
    dto.setId(user.getId());
    dto.setFirstName(user.getFirstName());
    dto.setLastName(user.getLastName());
    dto.setEmail(user.getEmail());
    dto.setPhoneNumber(user.getPhone());
    // ... 更多字段
    return dto;
}

问题显而易见:

  • 🔴 代码重复,维护困难
  • 🔴 容易遗漏字段或写错字段名
  • 🔴 当对象结构变化时,需要手动更新所有映射代码
  • 🔴 性能问题(如果使用反射)

MapStruct的解决方案

1
2
3
4
5
6
7
8
// MapStruct方式 - 简洁优雅
@Mapper
public interface UserMapper {
    @Mapping(source = "phone", target = "phoneNumber")
    UserDTO toDTO(User user);
    
    User toEntity(UserDTO dto);
}

优势立即显现:

  • ✅ 代码简洁,一目了然
  • ✅ 自动字段匹配,减少错误
  • ✅ 编译时验证,及早发现问题
  • ✅ 高性能,无反射开销

环境搭建与配置

Maven配置

pom.xml中添加依赖:

 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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<properties>
    <mapstruct.version>1.5.5.Final</mapstruct.version>
    <lombok.version>1.18.30</lombok.version>
</properties>

<dependencies>
    <!-- MapStruct核心依赖 -->
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>${mapstruct.version}</version>
    </dependency>
    
    <!-- Lombok支持(可选但推荐) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${lombok.version}</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.11.0</version>
            <configuration>
                <source>11</source>
                <target>11</target>
                <annotationProcessorPaths>
                    <!-- MapStruct注解处理器 -->
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${mapstruct.version}</version>
                    </path>
                    <!-- Lombok注解处理器 -->
                    <path>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok</artifactId>
                        <version>${lombok.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>

Gradle配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
plugins {
    id 'java'
    id 'org.springframework.boot' version '3.1.0'
}

ext {
    mapstructVersion = '1.5.5.Final'
    lombokVersion = '1.18.30'
}

dependencies {
    implementation "org.mapstruct:mapstruct:${mapstructVersion}"
    implementation "org.projectlombok:lombok:${lombokVersion}"
    
    annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"
    annotationProcessor "org.projectlombok:lombok:${lombokVersion}"
    annotationProcessor "org.projectlombok:lombok-mapstruct-binding:0.2.0"
}

第一个MapStruct示例

让我们从最简单的例子开始,创建一个用户实体和DTO的映射。

1. 定义实体类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Data  // Lombok注解
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Long id;
    private String firstName;
    private String lastName;
    private String email;
    private String phone;
    private LocalDate birthDate;
    private Boolean active;
}

2. 定义DTO类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserDTO {
    private Long id;
    private String firstName;
    private String lastName;
    private String email;
    private String phoneNumber;  // 注意:字段名与实体不同
    private String birthDateStr; // 类型也不同
    private Boolean isActive;    // 字段名略有不同
}

3. 创建Mapper接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@Mapper(componentModel = "spring")  // Spring组件
public interface UserMapper {
    
    @Mapping(source = "phone", target = "phoneNumber")
    @Mapping(source = "birthDate", target = "birthDateStr", 
             dateFormat = "yyyy-MM-dd")
    @Mapping(source = "active", target = "isActive")
    UserDTO toDTO(User user);
    
    @InheritInverseConfiguration  // 自动反向配置
    User toEntity(UserDTO dto);
    
    // 批量转换
    List<UserDTO> toDTOList(List<User> users);
}

4. 生成的代码

编译后,MapStruct会自动生成实现类:

 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
@Component  // 因为componentModel = "spring"
public class UserMapperImpl implements UserMapper {
    
    @Override
    public UserDTO toDTO(User user) {
        if (user == null) {
            return null;
        }
        
        UserDTO userDTO = new UserDTO();
        userDTO.setId(user.getId());
        userDTO.setFirstName(user.getFirstName());
        userDTO.setLastName(user.getLastName());
        userDTO.setEmail(user.getEmail());
        userDTO.setPhoneNumber(user.getPhone());
        userDTO.setIsActive(user.getActive());
        
        if (user.getBirthDate() != null) {
            userDTO.setBirthDateStr(
                DateTimeFormatter.ofPattern("yyyy-MM-dd")
                    .format(user.getBirthDate())
            );
        }
        
        return userDTO;
    }
    
    @Override
    public User toEntity(UserDTO dto) {
        // 自动生成的反向映射逻辑
        // ...
    }
}

5. 使用映射器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@Service
public class UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    public UserDTO getUserDTO(Long id) {
        User user = userRepository.findById(id);
        return userMapper.toDTO(user);  // 一行搞定!
    }
    
    public User createUser(UserDTO dto) {
        User user = userMapper.toEntity(dto);
        return userRepository.save(user);
    }
}

基本映射用法

字段名映射

1
2
3
4
5
6
7
8
@Mapper
public interface ProductMapper {
    
    @Mapping(source = "productName", target = "name")
    @Mapping(source = "productPrice", target = "price")
    @Mapping(source = "productDescription", target = "desc")
    ProductDTO toDTO(Product product);
}

类型转换

MapStruct支持多种自动类型转换:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@Mapper
public interface ConversionMapper {
    
    // 数字类型转换
    @Mapping(source = "count", target = "countStr")
    TargetObject convert(SourceObject source);
    
    // 日期格式化
    @Mapping(source = "createTime", target = "createTimeStr", 
             dateFormat = "yyyy-MM-dd HH:mm:ss")
    TargetObject convertDate(SourceObject source);
    
    // 布尔值转换
    @Mapping(source = "enabled", target = "status", 
             expression = "java(source.getEnabled() ? \"ACTIVE\" : \"INACTIVE\")")
    TargetObject convertBoolean(SourceObject source);
}

常量赋值

1
2
3
4
5
6
7
8
@Mapper
public interface ConstantMapper {
    
    @Mapping(target = "status", constant = "CREATED")
    @Mapping(target = "version", constant = "1.0")
    @Mapping(target = "createdBy", constant = "SYSTEM")
    UserDTO toDTO(User user);
}

默认值

1
2
3
4
5
6
7
8
@Mapper
public interface DefaultMapper {
    
    @Mapping(target = "country", defaultValue = "CN")
    @Mapping(target = "language", defaultValue = "zh_CN")
    @Mapping(target = "timezone", defaultValue = "Asia/Shanghai")
    UserProfileDTO toDTO(UserProfile profile);
}

忽略字段

1
2
3
4
5
6
7
8
@Mapper
public interface IgnoreMapper {
    
    @Mapping(target = "password", ignore = true)
    @Mapping(target = "internalId", ignore = true)
    @Mapping(target = "createdAt", ignore = true)
    UserDTO toSafeDTO(User user);
}

高级映射技巧

自定义方法

 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
@Mapper
public interface AdvancedUserMapper {
    
    @Mapping(source = "firstName", target = "fullName", 
             qualifiedByName = "buildFullName")
    @Mapping(source = "email", target = "emailDomain", 
             qualifiedByName = "extractDomain")
    UserDTO toDTO(User user);
    
    @Named("buildFullName")
    default String buildFullName(String firstName, String lastName) {
        if (firstName == null && lastName == null) {
            return null;
        }
        return (firstName != null ? firstName : "") + 
               " " + 
               (lastName != null ? lastName : "");
    }
    
    @Named("extractDomain")
    default String extractDomain(String email) {
        if (email == null || !email.contains("@")) {
            return null;
        }
        return email.substring(email.indexOf("@") + 1);
    }
}

多源对象映射

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Mapper
public interface MultiSourceMapper {
    
    @Mapping(source = "user.id", target = "userId")
    @Mapping(source = "user.name", target = "userName")
    @Mapping(source = "address.street", target = "street")
    @Mapping(source = "address.city", target = "city")
    @Mapping(source = "profile.bio", target = "biography")
    UserDetailDTO combine(User user, Address address, Profile profile);
}

条件映射

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@Mapper
public interface ConditionalMapper {
    
    @Mapping(target = "discountedPrice", 
             expression = "java(calculateDiscount(product))")
    ProductDTO toDTO(Product product);
    
    default BigDecimal calculateDiscount(Product product) {
        if (product.getCategory().equals("VIP")) {
            return product.getPrice().multiply(new BigDecimal("0.8"));
        }
        return product.getPrice();
    }
}

枚举映射

 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
// 源枚举
public enum UserStatus {
    ACTIVE, INACTIVE, PENDING
}

// 目标枚举
public enum UserState {
    ENABLED, DISABLED, WAITING
}

@Mapper
public interface EnumMapper {
    
    @ValueMapping(source = "ACTIVE", target = "ENABLED")
    @ValueMapping(source = "INACTIVE", target = "DISABLED")
    @ValueMapping(source = "PENDING", target = "WAITING")
    UserState mapStatus(UserStatus status);
    
    // 或使用表达式
    @Mapping(target = "state", 
             expression = "java(mapUserStatus(user.getStatus()))")
    UserDTO toDTO(User user);
    
    default UserState mapUserStatus(UserStatus status) {
        return switch (status) {
            case ACTIVE -> UserState.ENABLED;
            case INACTIVE -> UserState.DISABLED;
            case PENDING -> UserState.WAITING;
        };
    }
}

集合与复杂对象映射

集合映射

 1
 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 CollectionMapper {
    
    // List映射
    List<UserDTO> toDTOList(List<User> users);
    
    // Set映射
    Set<UserDTO> toDTOSet(Set<User> users);
    
    // Map映射
    Map<String, UserDTO> toDTOMap(Map<String, User> userMap);
    
    // 嵌套集合映射
    @Mapping(source = "tags", target = "tagNames")
    ArticleDTO toDTO(Article article);
    
    // 自定义集合转换
    default List<String> mapTags(List<Tag> tags) {
        return tags.stream()
                   .map(Tag::getName)
                   .collect(Collectors.toList());
    }
}

嵌套对象映射

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@Mapper(uses = {AddressMapper.class, ProfileMapper.class})
public interface ComplexUserMapper {
    
    @Mapping(source = "personalInfo.firstName", target = "firstName")
    @Mapping(source = "personalInfo.lastName", target = "lastName")
    @Mapping(source = "contactInfo.email", target = "email")
    @Mapping(source = "contactInfo.phone", target = "phoneNumber")
    // address和profile会自动使用对应的Mapper
    UserCompleteDTO toDTO(User user);
}

@Mapper
public interface AddressMapper {
    AddressDTO toDTO(Address address);
}

@Mapper
public interface ProfileMapper {
    ProfileDTO toDTO(Profile profile);
}

更新现有对象

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Mapper
public interface UpdateMapper {
    
    @Mapping(target = "id", ignore = true)  // 不更新ID
    @Mapping(target = "createdAt", ignore = true)  // 不更新创建时间
    @Mapping(target = "updatedAt", expression = "java(java.time.LocalDateTime.now())")
    void updateUserFromDTO(UserDTO dto, @MappingTarget User user);
}

// 使用方式
@Service
public class UserService {
    
    @Autowired
    private UpdateMapper updateMapper;
    
    public User updateUser(Long id, UserDTO dto) {
        User existingUser = userRepository.findById(id);
        updateMapper.updateUserFromDTO(dto, existingUser);
        return userRepository.save(existingUser);
    }
}

最佳实践与性能优化

1. 组件模型选择

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 推荐:Spring环境下使用spring组件模型
@Mapper(componentModel = "spring")
public interface UserMapper {
    // ...
}

// CDI环境
@Mapper(componentModel = "cdi")
public interface UserMapper {
    // ...
}

// 默认方式(手动获取实例)
@Mapper
public interface UserMapper {
    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
    // ...
}

2. 继承配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@Mapper
public interface BaseMapper {
    
    @Mapping(target = "createdAt", ignore = true)
    @Mapping(target = "updatedAt", ignore = true)
    @Mapping(target = "version", ignore = true)
    BaseDTO toDTO(BaseEntity entity);
}

// 继承基础配置
@Mapper
public interface UserMapper extends BaseMapper {
    
    @InheritConfiguration  // 继承父接口配置
    @Mapping(source = "phone", target = "phoneNumber")
    UserDTO toDTO(User user);
}

3. 性能优化技巧

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Mapper(
    componentModel = "spring",
    unmappedTargetPolicy = ReportingPolicy.ERROR,  // 未映射字段报错
    nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE  // 忽略null值
)
public interface OptimizedMapper {
    
    // 对于大型对象,考虑使用Builder模式
    @Builder
    @Mapping(source = "user.profile.bio", target = "biography")
    UserDTO toDTO(User user);
}

4. 调试与验证

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@Mapper(
    componentModel = "spring",
    unmappedTargetPolicy = ReportingPolicy.WARN,  // 警告未映射字段
    unmappedSourcePolicy = ReportingPolicy.WARN   // 警告未使用源字段
)
public interface DebuggableMapper {
    
    // 使用@BeanMapping进行详细配置
    @BeanMapping(
        nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_NULL,
        nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS
    )
    UserDTO toDTO(User user);
}

实战案例

案例1:电商订单系统

假设我们有一个电商系统,需要处理订单数据的转换:

  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
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// 实体类
@Entity
@Data
public class Order {
    private Long id;
    private String orderNumber;
    private LocalDateTime orderTime;
    private OrderStatus status;
    private BigDecimal totalAmount;
    private User customer;
    private List<OrderItem> items;
    private Address shippingAddress;
}

@Entity  
@Data
public class OrderItem {
    private Long id;
    private Product product;
    private Integer quantity;
    private BigDecimal unitPrice;
    private BigDecimal subtotal;
}

// DTO类
@Data
public class OrderDTO {
    private Long orderId;
    private String orderNumber;
    private String orderTimeStr;
    private String statusName;
    private BigDecimal totalAmount;
    private String customerName;
    private List<OrderItemDTO> items;
    private String shippingAddressStr;
    private Integer totalItems;
    private BigDecimal averageItemPrice;
}

@Data
public class OrderItemDTO {
    private String productName;
    private String productCode;
    private Integer quantity;
    private BigDecimal unitPrice;
    private BigDecimal subtotal;
}
// Mapper实现
@Mapper(
    componentModel = "spring",
    uses = {OrderItemMapper.class},
    imports = {DateTimeFormatter.class, Collectors.class}
)
public interface OrderMapper {
    
    @Mapping(source = "id", target = "orderId")
    @Mapping(source = "orderTime", target = "orderTimeStr", 
             dateFormat = "yyyy-MM-dd HH:mm:ss")
    @Mapping(source = "status", target = "statusName", 
             qualifiedByName = "mapOrderStatus")
    @Mapping(source = "customer.firstName", target = "customerName", 
             qualifiedByName = "buildCustomerName")
    @Mapping(source = "shippingAddress", target = "shippingAddressStr", 
             qualifiedByName = "formatAddress")
    @Mapping(target = "totalItems", 
             expression = "java(calculateTotalItems(order.getItems()))")
    @Mapping(target = "averageItemPrice", 
             expression = "java(calculateAveragePrice(order.getItems()))")
    OrderDTO toDTO(Order order);
    
    @Named("mapOrderStatus")
    default String mapOrderStatus(OrderStatus status) {
        return switch (status) {
            case PENDING -> "待处理";
            case CONFIRMED -> "已确认";
            case SHIPPED -> "已发货";
            case DELIVERED -> "已送达";
            case CANCELLED -> "已取消";
        };
    }
    
    @Named("buildCustomerName")
    default String buildCustomerName(String firstName, String lastName) {
        return (firstName != null ? firstName : "") + 
               (lastName != null ? " " + lastName : "");
    }
    
    @Named("formatAddress")
    default String formatAddress(Address address) {
        if (address == null) return null;
        return String.format("%s %s, %s, %s", 
                           address.getStreet(), 
                           address.getCity(), 
                           address.getState(), 
                           address.getZipCode());
    }
    
    default Integer calculateTotalItems(List<OrderItem> items) {
        return items.stream()
                   .mapToInt(OrderItem::getQuantity)
                   .sum();
    }
    
    default BigDecimal calculateAveragePrice(List<OrderItem> items) {
        if (items.isEmpty()) return BigDecimal.ZERO;
        BigDecimal total = items.stream()
                               .map(OrderItem::getUnitPrice)
                               .reduce(BigDecimal.ZERO, BigDecimal::add);
        return total.divide(new BigDecimal(items.size()), 2, RoundingMode.HALF_UP);
    }
}

@Mapper(componentModel = "spring")
public interface OrderItemMapper {
    
    @Mapping(source = "product.name", target = "productName")
    @Mapping(source = "product.code", target = "productCode")
    OrderItemDTO toDTO(OrderItem item);
}

案例2:用户权限系统

 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
// 复杂的用户权限映射
@Mapper(
    componentModel = "spring",
    uses = {RoleMapper.class, PermissionMapper.class}
)
public interface UserSecurityMapper {
    
    @Mapping(source = "user.id", target = "userId")
    @Mapping(source = "user.username", target = "username")
    @Mapping(source = "user.roles", target = "roleNames", 
             qualifiedByName = "extractRoleNames")
    @Mapping(source = "user.roles", target = "permissions", 
             qualifiedByName = "extractAllPermissions")
    @Mapping(target = "hasAdminRole", 
             expression = "java(checkAdminRole(user.getRoles()))")
    UserSecurityDTO toSecurityDTO(User user);
    
    @Named("extractRoleNames")
    default Set<String> extractRoleNames(Set<Role> roles) {
        return roles.stream()
                   .map(Role::getName)
                   .collect(Collectors.toSet());
    }
    
    @Named("extractAllPermissions")
    default Set<String> extractAllPermissions(Set<Role> roles) {
        return roles.stream()
                   .flatMap(role -> role.getPermissions().stream())
                   .map(Permission::getName)
                   .collect(Collectors.toSet());
    }
    
    default Boolean checkAdminRole(Set<Role> roles) {
        return roles.stream()
                   .anyMatch(role -> "ADMIN".equals(role.getName()));
    }
}

常见问题与解决方案

1. 编译时错误

问题: “No property named ‘xxx’ exists in source parameter”

解决方案:

1
2
// 确保字段名正确,或使用@Mapping指定
@Mapping(source = "firstName", target = "fname")  // 明确指定映射关系

问题: 循环依赖

解决方案:

1
2
3
4
5
6
@Mapper(uses = {DepartmentMapper.class})
public interface EmployeeMapper {
    
    @Mapping(target = "department.employees", ignore = true)  // 忽略循环引用
    EmployeeDTO toDTO(Employee employee);
}

2. 运行时问题

问题: NullPointerException

解决方案:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Mapper(
    nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE,
    nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS
)
public interface SafeMapper {
    
    @Mapping(target = "address", 
             expression = "java(user.getAddress() != null ? mapAddress(user.getAddress()) : null)")
    UserDTO toDTO(User user);
}

3. 性能问题

问题: 大量对象转换性能差

解决方案:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@Mapper(
    componentModel = "spring",
    implementationName = "Fast<CLASS_NAME>Impl",  // 自定义实现类名
    implementationPackage = "com.example.mapper.impl"  // 指定包名
)
public interface FastMapper {
    
    // 使用Stream API进行批量转换
    default List<UserDTO> toBatchDTO(List<User> users) {
        return users.parallelStream()  // 并行处理
                   .map(this::toDTO)
                   .collect(Collectors.toList());
    }
}

4. 调试技巧

启用详细日志:

1
2
3
4
5
6
7
8
@Mapper(
    componentModel = "spring",
    unmappedTargetPolicy = ReportingPolicy.WARN,
    unmappedSourcePolicy = ReportingPolicy.WARN
)
public interface DebuggableMapper {
    // MapStruct会在编译时输出详细的映射信息
}

查看生成的代码: 生成的实现类位于:target/generated-sources/annotations/


🎯 总结

MapStruct是Java开发中处理对象映射的强大工具,通过本教程你已经掌握了:

核心知识点

  • ✅ MapStruct的基本概念和优势
  • ✅ 环境搭建和配置方法
  • ✅ 基础映射和高级映射技巧
  • ✅ 集合和复杂对象的处理
  • ✅ 最佳实践和性能优化

实用技巧

  • 🔧 使用@Mapping进行字段映射
  • 🔧 通过@Named创建自定义转换方法
  • 🔧 利用@InheritConfiguration减少重复配置
  • 🔧 使用expression处理复杂业务逻辑

最佳实践

  1. 选择合适的组件模型(推荐使用componentModel = "spring"
  2. 合理使用继承配置减少代码重复
  3. 处理null值避免运行时异常
  4. 注意循环依赖问题
  5. 关注性能优化,特别是大批量数据转换

下一步建议

  1. 深入实践:在实际项目中应用MapStruct
  2. 探索高级特性:如装饰器模式、条件映射等
  3. 集成测试:编写单元测试验证映射逻辑
  4. 监控性能:在生产环境中观察映射性能

MapStruct让对象映射变得简单而高效,掌握了这个工具,你将在Java开发中如虎添翼!🚀


推荐阅读:

Happy Coding! 🎉

NovaBryan的博客
使用 Hugo 构建
主题 StackJimmy 设计