MapStruct完全学习指南

MapStruct完全学习指南

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

📚 目录

MapStruct是什么?

为什么要使用MapStruct?

环境搭建与配置

第一个MapStruct示例

基本映射用法

高级映射技巧

集合与复杂对象映射

最佳实践与性能优化

实战案例

常见问题与解决方案

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

1.5.5.Final

1.18.30

org.mapstruct

mapstruct

${mapstruct.version}

org.projectlombok

lombok

${lombok.version}

provided

org.apache.maven.plugins

maven-compiler-plugin

3.11.0

11

11

org.mapstruct

mapstruct-processor

${mapstruct.version}

org.projectlombok

lombok

${lombok.version}

org.projectlombok

lombok-mapstruct-binding

0.2.0

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 toDTOList(List 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 toDTOList(List users);

// Set映射

Set toDTOSet(Set users);

// Map映射

Map toDTOMap(Map userMap);

// 嵌套集合映射

@Mapping(source = "tags", target = "tagNames")

ArticleDTO toDTO(Article article);

// 自定义集合转换

default List mapTags(List 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 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 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 items) {

return items.stream()

.mapToInt(OrderItem::getQuantity)

.sum();

}

default BigDecimal calculateAveragePrice(List 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 extractRoleNames(Set roles) {

return roles.stream()

.map(Role::getName)

.collect(Collectors.toSet());

}

@Named("extractAllPermissions")

default Set extractAllPermissions(Set roles) {

return roles.stream()

.flatMap(role -> role.getPermissions().stream())

.map(Permission::getName)

.collect(Collectors.toSet());

}

default Boolean checkAdminRole(Set 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 = "FastImpl", // 自定义实现类名

implementationPackage = "com.example.mapper.impl" // 指定包名

)

public interface FastMapper {

// 使用Stream API进行批量转换

default List toBatchDTO(List 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处理复杂业务逻辑

最佳实践

选择合适的组件模型(推荐使用componentModel = "spring")

合理使用继承配置减少代码重复

处理null值避免运行时异常

注意循环依赖问题

关注性能优化,特别是大批量数据转换

下一步建议

深入实践:在实际项目中应用MapStruct

探索高级特性:如装饰器模式、条件映射等

集成测试:编写单元测试验证映射逻辑

监控性能:在生产环境中观察映射性能

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

推荐阅读:

MapStruct官方文档

Spring Boot集成最佳实践

Java性能优化指南

Happy Coding! 🎉

相关文章

中年转行卖保险:人脉榨干,收入腰斩
beat365手机版官方

中年转行卖保险:人脉榨干,收入腰斩

📅 08-04 👁️ 1411