MapStruct应用指南
1. MapStruct简介与核心价值
MapStruct 是一个开源的Java注解处理器,用于生成类型安全、高性能且无依赖的Bean映射代码。它在编译时生成映射实现,避免了运行时反射带来的性能损耗。
官方网站: https://mapstruct.org/
官方文档: https://mapstruct.org/documentation/stable/reference/html/
1.1 MapStruct的核心优势
- 编译时代码生成 - 零运行时性能损耗
- 类型安全 - 编译时检查类型匹配
- 易于调试 - 生成的代码清晰可读
- 无外部依赖 - 仅在编译时需要
- Spring集成 - 无缝集成Spring Framework
2. MapStruct核心注解完整参考
2.1 注解总览表
注解类别 | 注解名称 | 主要用途 | 使用位置 |
---|---|---|---|
基础注解 | @Mapper | 标记映射器接口 | 接口 |
@Mapping | 配置字段映射规则 | 方法 | |
方法注解 | @Named | 命名自定义方法 | 方法 |
@BeforeMapping | 映射前执行 | 方法 | |
@AfterMapping | 映射后执行 | 方法 | |
枚举注解 | @ValueMapping | 枚举值映射 | 方法 |
@EnumMapping | 枚举策略配置 | 方法 | |
行为控制 | @BeanMapping | Bean级别映射控制 | 方法 |
@MappingTarget | 标记更新目标 | 参数 | |
@Context | 传递上下文 | 参数 | |
配置继承 | @InheritConfiguration | 继承映射配置 | 方法 |
@InheritInverseConfiguration | 继承反向配置 | 方法 | |
高级特性 | @DecoratedWith | 装饰器模式 | 接口 |
@SubclassMapping | 子类映射 | 方法 | |
@Condition | 条件映射 | 方法 |
2.2 @Mapper 详细配置
@Mapper(
componentModel = "spring", // 组件模型
uses = {DateMapper.class}, // 依赖的其他映射器
imports = {LocalDateTime.class}, // 导入类型
unmappedTargetPolicy = ReportingPolicy.IGNORE, // 未映射字段策略
nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT,
nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE
)
public interface UserMapper {
// 映射方法
}
@Mapper 完整参数表:
参数 | 类型 | 默认值 | 说明 | 示例 |
---|---|---|---|---|
componentModel | String | “default” | 组件模型类型 | “spring”, “cdi”, “jsr330” |
uses | Class[] | {} | 依赖的映射器或工具类 | {DateMapper.class} |
imports | Class[] | {} | 表达式中使用的类型 | {LocalDateTime.class} |
unmappedTargetPolicy | ReportingPolicy | IGNORE | 未映射目标字段策略 | IGNORE, WARN, ERROR |
unmappedSourcePolicy | ReportingPolicy | IGNORE | 未映射源字段策略 | IGNORE, WARN, ERROR |
nullValueMappingStrategy | NullValueMappingStrategy | RETURN_NULL | null值映射策略 | RETURN_NULL, RETURN_DEFAULT |
nullValuePropertyMappingStrategy | NullValuePropertyMappingStrategy | SET_TO_NULL | null属性映射策略 | SET_TO_NULL, IGNORE, SET_TO_DEFAULT |
nullValueCheckStrategy | NullValueCheckStrategy | ON_IMPLICIT_CONVERSION | null值检查策略 | ON_IMPLICIT_CONVERSION, ALWAYS |
componentModel 详解:
值 | 说明 | 使用方式 | 适用场景 |
---|---|---|---|
"default" | 默认模式,无依赖注入 | MyMapper.INSTANCE.map(obj) | 简单项目,无框架依赖 |
"spring" | Spring Bean模式 | @Autowired MyMapper mapper | Spring Boot项目(推荐) |
"cdi" | CDI Bean模式 | @Inject MyMapper mapper | Java EE项目 |
"jsr330" | JSR-330注解模式 | @Inject MyMapper mapper | 通用依赖注入 |
Spring模式使用示例:
// 1. 定义映射器(自动生成Spring Bean)
@Mapper(componentModel = "spring")
public interface UserMapper {
UserDTO toDTO(User user);
}
// 2. 在Service中使用(Spring自动注入)
@Service
public class UserService {
@Autowired
private UserMapper userMapper; // Spring自动注入
public UserDTO getUser(Long id) {
User user = userRepository.findById(id);
return userMapper.toDTO(user); // 直接调用
}
}
2.3 @Mapping 详细配置
@Mapping 完整参数表:
参数 | 类型 | 说明 | 示例 |
---|---|---|---|
source | String | 源字段名 | source = "firstName" |
target | String | 目标字段名 | target = "fullName" |
expression | String | Java表达式 | expression = "java(source.getAge() + 1)" |
constant | String | 常量值 | constant = "DEFAULT_VALUE" |
defaultValue | String | 默认值 | defaultValue = "0" |
ignore | boolean | 是否忽略 | ignore = true |
qualifiedByName | String | 自定义方法名 | qualifiedByName = "formatDate" |
conditionExpression | String | 条件表达式 | conditionExpression = "java(source != null)" |
dependsOn | String[] | 依赖字段 | dependsOn = {"firstName", "lastName"} |
nullValuePropertyMappingStrategy | NullValuePropertyMappingStrategy | null值策略 | IGNORE , SET_TO_NULL , SET_TO_DEFAULT |
@Mapping 使用示例:
@Mapper(componentModel = "spring")
public interface UserMapper {
// 基础字段映射
@Mapping(source = "id", target = "userId")
@Mapping(source = "username", target = "userName")
// 表达式映射
@Mapping(target = "fullName",
expression = "java(user.getFirstName() + \" \" + user.getLastName())")
// 常量映射
@Mapping(target = "type", constant = "USER")
// 默认值映射
@Mapping(target = "status", defaultValue = "ACTIVE")
// 忽略字段
@Mapping(target = "password", ignore = true)
// 自定义方法映射
@Mapping(target = "birthDateString", source = "birthDate", qualifiedByName = "formatDate")
// 条件映射
@Mapping(target = "email", source = "email",
conditionExpression = "java(user.getEmail() != null && user.getEmail().contains(\"@\"))")
UserDTO toDTO(User user);
@Named("formatDate")
default String formatDate(LocalDate date) {
return date != null ? date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) : null;
}
}
2.4 枚举映射注解
@ValueMapping 参数表:
参数 | 类型 | 说明 | 示例 |
---|---|---|---|
source | String | 源枚举值 | source = "ACTIVE" |
target | String | 目标枚举值 | target = "ENABLED" |
特殊常量:
MappingConstants.NULL
- 处理null值MappingConstants.ANY_REMAINING
- 处理未映射的值MappingConstants.ANY_UNMAPPED
- 处理未明确映射的值
@Mapper
public interface StatusMapper {
@ValueMapping(source = "ACTIVE", target = "ENABLED")
@ValueMapping(source = "INACTIVE", target = "DISABLED")
@ValueMapping(source = "PENDING", target = "WAITING")
// 处理null值
@ValueMapping(source = MappingConstants.NULL, target = "UNKNOWN")
// 处理其他未映射值
@ValueMapping(source = MappingConstants.ANY_REMAINING, target = "OTHER")
TargetStatus mapStatus(SourceStatus status);
}
2.5 行为控制注解
@BeanMapping 参数表:
参数 | 类型 | 说明 | 可选值 |
---|---|---|---|
nullValueMappingStrategy | NullValueMappingStrategy | null对象映射策略 | RETURN_NULL, RETURN_DEFAULT |
nullValuePropertyMappingStrategy | NullValuePropertyMappingStrategy | null属性映射策略 | SET_TO_NULL, IGNORE, SET_TO_DEFAULT |
nullValueCheckStrategy | NullValueCheckStrategy | null检查策略 | ON_IMPLICIT_CONVERSION, ALWAYS |
ignoreUnmappedSourceProperties | String[] | 忽略的源属性 | {“password”, “internal”} |
ignoreByDefault | boolean | 默认忽略所有字段 | true, false |
@Mapper(componentModel = "spring")
public interface UserUpdateMapper {
// 更新映射 - 忽略null值
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
@Mapping(target = "id", ignore = true)
@Mapping(target = "createTime", ignore = true)
void updateUser(UserDTO dto, @MappingTarget User user);
// 安全映射 - 忽略敏感字段
@BeanMapping(ignoreUnmappedSourceProperties = {"password", "internalCode"})
UserDTO toSafeDTO(User user);
}
3. MapStruct基础使用案例 - 完整代码实现
3.1 项目依赖配置 - pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>mapstruct-demo</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<mapstruct.version>1.6.3</mapstruct.version>
<spring-boot.version>2.7.18</spring-boot.version>
<lombok.version>1.18.28</lombok.version>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- 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.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</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>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
3.2 实体类代码
User.java
package com.example.mapstruct.entity;
import com.example.mapstruct.enums.UserStatus;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long id;
private String username;
private String email;
private String firstName;
private String lastName;
private LocalDateTime createTime;
private UserStatus status;
private Address address;
private List<Role> roles;
}
Address.java
package com.example.mapstruct.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Address {
private String city;
private String district;
private String zipCode;
private String country;
}
Role.java
package com.example.mapstruct.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Role {
private Long id;
private String name;
private String description;
}
3.3 枚举类代码
UserStatus.java
package com.example.mapstruct.enums;
public enum UserStatus {
ACTIVE("活跃"),
INACTIVE("未激活"),
SUSPENDED("已暂停"),
DELETED("已删除");
private final String description;
UserStatus(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
StatusCode.java
package com.example.mapstruct.enums;
public enum StatusCode {
OK("正常"),
PENDING("待处理"),
BLOCKED("已阻止"),
REMOVED("已移除");
private final String description;
StatusCode(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
3.4 DTO类代码
UserDTO.java
package com.example.mapstruct.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserDTO {
private Long userId;
private String userName;
private String email;
private String fullName;
private String createTimeString;
private String statusDescription;
private AddressDTO address;
private List<RoleDTO> roles;
}
UserSimpleDTO.java
package com.example.mapstruct.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserSimpleDTO {
private Long userId;
private String userName;
private String email;
private String fullName;
private String status;
}
AddressDTO.java
package com.example.mapstruct.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AddressDTO {
private String city;
private String district;
private String zipCode;
private String country;
private String fullAddress;
}
RoleDTO.java
package com.example.mapstruct.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RoleDTO {
private Long id;
private String name;
private String description;
}
3.5 MapStruct映射器代码
UserMapper.java - 基础映射器
package com.example.mapstruct.mapper;
import com.example.mapstruct.dto.UserDTO;
import com.example.mapstruct.dto.UserSimpleDTO;
import com.example.mapstruct.entity.User;
import com.example.mapstruct.enums.UserStatus;
import org.mapstruct.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface UserMapper {
// 实体转完整DTO
@Mapping(source = "id", target = "userId")
@Mapping(source = "username", target = "userName")
@Mapping(target = "fullName", expression = "java(user.getFirstName() + \" \" + user.getLastName())")
@Mapping(target = "createTimeString", source = "createTime", qualifiedByName = "formatDateTime")
@Mapping(target = "statusDescription", source = "status", qualifiedByName = "mapStatusToString")
UserDTO toDTO(User user);
// 实体转简单DTO
@Mapping(source = "id", target = "userId")
@Mapping(source = "username", target = "userName")
@Mapping(target = "fullName", expression = "java(user.getFirstName() + \" \" + user.getLastName())")
@Mapping(target = "status", source = "status", qualifiedByName = "mapStatusToString")
UserSimpleDTO toSimpleDTO(User user);
// DTO转实体
@Mapping(source = "userId", target = "id")
@Mapping(source = "userName", target = "username")
@Mapping(target = "firstName", ignore = true)
@Mapping(target = "lastName", ignore = true)
@Mapping(target = "createTime", ignore = true)
@Mapping(target = "status", ignore = true)
User toEntity(UserDTO dto);
// 批量转换
List<UserDTO> toDTOList(List<User> users);
List<UserSimpleDTO> toSimpleDTOList(List<User> users);
// 更新映射
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
@Mapping(target = "id", ignore = true)
@Mapping(target = "createTime", ignore = true)
void updateUserFromDTO(UserDTO dto, @MappingTarget User user);
// 自定义方法
@Named("formatDateTime")
default String formatDateTime(LocalDateTime dateTime) {
if (dateTime == null) return null;
return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
@Named("mapStatusToString")
default String mapStatusToString(UserStatus status) {
return status != null ? status.getDescription() : "未知";
}
}
ComprehensiveMapper.java - 综合映射器(包含所有高级功能)
package com.example.mapstruct.mapper;
import com.example.mapstruct.dto.*;
import com.example.mapstruct.entity.*;
import com.example.mapstruct.enums.*;
import org.mapstruct.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface ComprehensiveMapper {
// ========== 基础映射 ==========
@Mapping(source = "id", target = "userId")
@Mapping(source = "username", target = "userName")
@Mapping(target = "fullName", expression = "java(buildFullName(user.getFirstName(), user.getLastName()))")
@Mapping(target = "createTimeString", source = "createTime", qualifiedByName = "formatDateTime")
@Mapping(target = "statusDescription", source = "status", qualifiedByName = "statusToDescription")
UserDTO entityToDto(User user);
@Mapping(source = "userId", target = "id")
@Mapping(source = "userName", target = "username")
@Mapping(target = "createTime", ignore = true)
@Mapping(target = "status", ignore = true)
User dtoToEntity(UserDTO dto);
// ========== 枚举映射 ==========
@ValueMapping(source = "ACTIVE", target = "OK")
@ValueMapping(source = "INACTIVE", target = "PENDING")
@ValueMapping(source = "SUSPENDED", target = "BLOCKED")
@ValueMapping(source = "DELETED", target = "REMOVED")
StatusCode userStatusToStatusCode(UserStatus userStatus);
@InheritInverseConfiguration
UserStatus statusCodeToUserStatus(StatusCode statusCode);
// ========== 嵌套对象映射 ==========
@Mapping(target = "fullAddress", expression = "java(buildFullAddress(address))")
AddressDTO addressToDto(Address address);
Address dtoToAddress(AddressDTO dto);
// ========== 集合映射 ==========
List<UserDTO> entitiesToDtos(List<User> users);
List<User> dtosToEntities(List<UserDTO> dtos);
List<RoleDTO> rolesToDtos(List<Role> roles);
// ========== 条件映射 ==========
@Mapping(source = "username", target = "userName",
conditionExpression = "java(user.getUsername() != null && user.getUsername().length() > 0)")
@Mapping(target = "fullName", source = ".", qualifiedByName = "conditionalFullName")
UserSimpleDTO toConditionalDto(User user);
@Named("conditionalFullName")
default String conditionalFullName(User user) {
if (user.getFirstName() != null && user.getLastName() != null) {
return user.getFirstName() + " " + user.getLastName();
}
return user.getUsername();
}
// ========== 多源映射 ==========
@Mapping(source = "user.id", target = "userId")
@Mapping(source = "user.username", target = "userName")
@Mapping(target = "fullName", expression = "java(prefix + \" \" + user.getFirstName() + \" \" + user.getLastName())")
@Mapping(target = "status", expression = "java(isVip ? \"VIP用户\" : user.getStatus().getDescription())")
UserSimpleDTO mapWithPrefix(User user, String prefix, boolean isVip);
// ========== 表达式映射 ==========
@Mapping(target = "userId", expression = "java(getLongFromMap(data, \"id\"))")
@Mapping(target = "userName", expression = "java(getStringFromMap(data, \"username\"))")
@Mapping(target = "email", expression = "java(getStringFromMap(data, \"email\"))")
@Mapping(target = "fullName", expression = "java(getStringFromMap(data, \"fullName\"))")
UserSimpleDTO mapFromMap(Map<String, Object> data);
// ========== 自定义方法 ==========
@Named("formatDateTime")
default String formatDateTime(LocalDateTime dateTime) {
if (dateTime == null) return null;
return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
@Named("statusToDescription")
default String statusToDescription(UserStatus status) {
return status != null ? status.getDescription() : "未知状态";
}
default String buildFullName(String firstName, String lastName) {
if (firstName == null && lastName == null) return "匿名用户";
if (firstName == null) return lastName;
if (lastName == null) return firstName;
return firstName + " " + lastName;
}
default String buildFullAddress(Address address) {
if (address == null) return "地址未知";
StringBuilder sb = new StringBuilder();
if (address.getCountry() != null) sb.append(address.getCountry()).append(" ");
if (address.getCity() != null) sb.append(address.getCity()).append(" ");
if (address.getDistrict() != null) sb.append(address.getDistrict()).append(" ");
if (address.getZipCode() != null) sb.append(address.getZipCode());
return sb.toString().trim();
}
default String getStringFromMap(Map<String, Object> map, String key) {
Object value = map.get(key);
return value != null ? value.toString() : null;
}
default Long getLongFromMap(Map<String, Object> map, String key) {
Object value = map.get(key);
if (value instanceof Number) {
return ((Number) value).longValue();
}
return null;
}
}
3.6 Spring Boot控制器代码
UserController.java
package com.example.mapstruct.controller;
import com.example.mapstruct.dto.*;
import com.example.mapstruct.entity.*;
import com.example.mapstruct.enums.*;
import com.example.mapstruct.mapper.ComprehensiveMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.*;
@RestController
@RequestMapping("/api")
public class UserController {
@Autowired
private ComprehensiveMapper mapper;
// 获取用户信息
@GetMapping("/users/{id}")
public UserDTO getUser(@PathVariable Long id) {
User user = createSampleUser(id);
return mapper.entityToDto(user);
}
// 获取用户列表
@GetMapping("/users")
public List<UserDTO> getUsers() {
List<User> users = Arrays.asList(
createSampleUser(1L),
createSampleUser(2L),
createSampleUser(3L)
);
return mapper.entitiesToDtos(users);
}
// 枚举映射测试
@GetMapping("/enum-mapping")
public Map<String, Object> testEnumMapping() {
Map<String, Object> result = new HashMap<>();
// UserStatus -> StatusCode
UserStatus userStatus = UserStatus.ACTIVE;
StatusCode statusCode = mapper.userStatusToStatusCode(userStatus);
result.put("userStatus", userStatus);
result.put("mappedStatusCode", statusCode);
// StatusCode -> UserStatus
UserStatus backStatus = mapper.statusCodeToUserStatus(statusCode);
result.put("backMappedUserStatus", backStatus);
return result;
}
// 条件映射测试
@GetMapping("/conditional-mapping")
public UserSimpleDTO testConditionalMapping() {
User user = createSampleUser(1L);
user.setUsername(""); // 测试条件映射
return mapper.toConditionalDto(user);
}
// 多源映射测试
@GetMapping("/multi-source-mapping")
public UserSimpleDTO testMultiSourceMapping() {
User user = createSampleUser(1L);
return mapper.mapWithPrefix(user, "[VIP]", true);
}
// Map映射测试
@GetMapping("/map-mapping")
public UserSimpleDTO testMapMapping() {
Map<String, Object> data = new HashMap<>();
data.put("id", 999L);
data.put("username", "mapuser");
data.put("email", "map@example.com");
data.put("fullName", "Map 用户");
return mapper.mapFromMap(data);
}
// 创建测试用户
@PostMapping("/users")
public UserDTO createUser(@RequestBody UserDTO userDTO) {
User user = mapper.dtoToEntity(userDTO);
user.setId(System.currentTimeMillis()); // 模拟生成ID
user.setCreateTime(LocalDateTime.now());
user.setStatus(UserStatus.ACTIVE);
return mapper.entityToDto(user);
}
// 映射效果对比
@GetMapping("/mapping-demo")
public Map<String, Object> mappingDemo() {
User user = createSampleUser(1L);
Map<String, Object> result = new HashMap<>();
result.put("原始User对象", user);
result.put("映射后UserDTO", mapper.entityToDto(user));
result.put("条件映射结果", mapper.toConditionalDto(user));
result.put("多源映射结果", mapper.mapWithPrefix(user, "[管理员]", false));
result.put("地址映射结果", mapper.addressToDto(user.getAddress()));
result.put("枚举映射结果", mapper.userStatusToStatusCode(user.getStatus()));
return result;
}
// 创建示例用户数据
private User createSampleUser(Long id) {
return User.builder()
.id(id)
.username("user" + id)
.email("user" + id + "@example.com")
.firstName("张")
.lastName("三" + id)
.createTime(LocalDateTime.now().minusDays(id))
.status(UserStatus.ACTIVE)
.address(Address.builder()
.city("北京市")
.district("朝阳区")
.zipCode("100001")
.country("中国")
.build())
.roles(Arrays.asList(
Role.builder().id(1L).name("USER").description("普通用户").build(),
Role.builder().id(2L).name("ADMIN").description("管理员").build()
))
.build();
}
}
3.7 Spring Boot启动类
MapStructApplication.java
package com.example.mapstruct;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MapStructApplication {
public static void main(String[] args) {
SpringApplication.run(MapStructApplication.class, args);
}
}
3.8 测试方法
- 编译项目:
mvn clean compile
- 启动应用:
mvn spring-boot:run
- 测试API接口:
GET http://localhost:8080/api/users/1
- 获取用户GET http://localhost:8080/api/users
- 获取用户列表GET http://localhost:8080/api/enum-mapping
- 枚举映射测试GET http://localhost:8080/api/conditional-mapping
- 条件映射测试GET http://localhost:8080/api/multi-source-mapping
- 多源映射测试GET http://localhost:8080/api/map-mapping
- Map映射测试GET http://localhost:8080/api/mapping-demo
- 完整映射演示
这样读者就有了一个完整可运行的MapStruct项目,包含了所有核心功能的代码实现。
4. MapStruct高级使用技巧
基于第3章的完整代码,我们已经在ComprehensiveMapper中实现了MapStruct的各种高级功能。本章详细解析这些功能的使用方法。
4.1 复杂表达式映射
在ComprehensiveMapper中,我们使用了多种表达式映射:
// 组合字段映射
@Mapping(target = "fullName", expression = "java(buildFullName(user.getFirstName(), user.getLastName()))")
// 自定义方法实现
default String buildFullName(String firstName, String lastName) {
if (firstName == null && lastName == null) return "匿名用户";
if (firstName == null) return lastName;
if (lastName == null) return firstName;
return firstName + " " + lastName;
}
应用场景:
- 字段组合(如姓+名)
- 数据格式化(如时间格式化)
- 复杂计算逻辑
4.2 条件映射
条件映射允许基于条件决定是否执行映射:
// 条件表达式映射
@Mapping(source = "username", target = "userName",
conditionExpression = "java(user.getUsername() != null && user.getUsername().length() > 0)")
// 自定义条件方法
@Named("conditionalFullName")
default String conditionalFullName(User user) {
if (user.getFirstName() != null && user.getLastName() != null) {
return user.getFirstName() + " " + user.getLastName();
}
return user.getUsername(); // 备选方案
}
应用场景:
- 数据校验
- 备选映射策略
- 安全性过滤
4.3 多源参数映射
从多个参数中获取数据进行映射:
// 多参数映射
@Mapping(source = "user.id", target = "userId")
@Mapping(source = "user.username", target = "userName")
@Mapping(target = "fullName", expression = "java(prefix + \" \" + user.getFirstName() + \" \" + user.getLastName())")
@Mapping(target = "status", expression = "java(isVip ? \"VIP用户\" : user.getStatus().getDescription())")
UserSimpleDTO mapWithPrefix(User user, String prefix, boolean isVip);
测试示例:
GET http://localhost:8080/api/multi-source-mapping
# 返回: {"userId":1,"userName":"user1","fullName":"[VIP] 张 三1","status":"VIP用户"}
4.4 Map数据源映射
从Map中提取数据进行映射:
// Map映射
@Mapping(target = "userId", expression = "java(getLongFromMap(data, \"id\"))")
@Mapping(target = "userName", expression = "java(getStringFromMap(data, \"username\"))")
UserSimpleDTO mapFromMap(Map<String, Object> data);
// 辅助方法
default String getStringFromMap(Map<String, Object> map, String key) {
Object value = map.get(key);
return value != null ? value.toString() : null;
}
测试示例:
GET http://localhost:8080/api/map-mapping
# 返回: {"userId":999,"userName":"mapuser","email":"map@example.com","fullName":"Map 用户"}
4.5 枚举映射详解
在ComprehensiveMapper中已经实现了完整的枚举映射功能:
// 枚举到枚举映射
@ValueMapping(source = "ACTIVE", target = "OK")
@ValueMapping(source = "INACTIVE", target = "PENDING")
@ValueMapping(source = "SUSPENDED", target = "BLOCKED")
@ValueMapping(source = "DELETED", target = "REMOVED")
StatusCode userStatusToStatusCode(UserStatus userStatus);
// 反向映射
@InheritInverseConfiguration
UserStatus statusCodeToUserStatus(StatusCode statusCode);
测试示例:
GET http://localhost:8080/api/enum-mapping
# 返回: {
# "userStatus": "ACTIVE",
# "mappedStatusCode": "OK",
# "backMappedUserStatus": "ACTIVE"
# }
映射规则:
UserStatus.ACTIVE
→StatusCode.OK
UserStatus.INACTIVE
→StatusCode.PENDING
UserStatus.SUSPENDED
→StatusCode.BLOCKED
UserStatus.DELETED
→StatusCode.REMOVED
### 4.6 集合映射
批量数据转换,性能优于逐个映射:
```java
// 批量映射方法
List<UserDTO> entitiesToDtos(List<User> users);
List<User> dtosToEntities(List<UserDTO> dtos);
List<RoleDTO> rolesToDtos(List<Role> roles);
测试示例:
GET http://localhost:8080/api/users
# 返回用户列表,所有User实体都被转换为UserDTO
4.7 嵌套对象映射
处理复杂对象结构的映射:
// 地址嵌套映射
@Mapping(target = "fullAddress", expression = "java(buildFullAddress(address))")
AddressDTO addressToDto(Address address);
// 自定义地址格式化
default String buildFullAddress(Address address) {
if (address == null) return "地址未知";
StringBuilder sb = new StringBuilder();
if (address.getCountry() != null) sb.append(address.getCountry()).append(" ");
if (address.getCity() != null) sb.append(address.getCity()).append(" ");
if (address.getDistrict() != null) sb.append(address.getDistrict()).append(" ");
if (address.getZipCode() != null) sb.append(address.getZipCode());
return sb.toString().trim();
}
4.8 性能优化与实用技巧
- 使用批量映射:避免在循环中逐个调用映射方法
- 合理使用表达式:复杂逻辑使用自定义方法而非表达式
- 避免过度映射:根据实际需要选择简单DTO或完整DTO
- 利用Spring Bean:使用componentModel="spring"避免重复实例化
- 条件映射:根据业务条件决定映射逻辑
- 多源映射:从多个参数构建复杂对象
通过这些高级技巧,MapStruct能够处理几乎所有的对象映射场景,既保证了性能又提供了灵活性。
5. 实际开发应用场景
@Component
public class UserFactory {
public User createUser() {
User user = new User();
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
user.setActive(true);
return user;
}
}
@Mapper(componentModel = “spring”, uses = UserFactory.class)
public interface UserFactoryMapper {
@Mapping(target = "id", ignore = true)
User toEntity(UserDTO userDTO);
UserDTO toDTO(User user);
}
### 4.9 null值处理策略
```java
// IGNORE策略 - null值被忽略,保持目标对象原值
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
void updateWithIgnoreStrategy(UserDTO dto, @MappingTarget User user);
// SET_TO_NULL策略 - null值会设置目标对象为null
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_NULL)
void updateWithSetToNullStrategy(UserDTO dto, @MappingTarget User user);
// SET_TO_DEFAULT策略 - null值会设置目标对象为默认值
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT)
void updateWithSetToDefaultStrategy(UserDTO dto, @MappingTarget User user);
4.10 Lombok集成最佳实践
MapStruct与Lombok完美配合,但需要正确的配置。基于官方文档Lombok集成指南:
<!-- 完整的Maven配置 -->
<properties>
<mapstruct.version>1.6.3</mapstruct.version>
<lombok.version>1.18.30</lombok.version>
</properties>
<dependencies>
<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>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<!-- Lombok 1.18.16+必需的绑定依赖 -->
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
4.11 Builder模式支持
MapStruct原生支持Builder模式,自动检测和使用构建器:
// 使用Builder的UserDTO示例
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserDTO {
private Long id;
private String username;
private String email;
private String firstName;
private String lastName;
private Integer age;
// ... 其他字段
}
@Mapper(componentModel = "spring")
public interface UserBuilderMapper {
// MapStruct自动使用Builder模式
UserDTO entityToDto(User user);
// 自定义Builder配置
@BeanMapping(builder = @Builder(disableBuilder = true))
User dtoToEntityWithoutBuilder(UserDTO dto);
}
4.12 流式映射(Stream Mapping)
MapStruct支持Java 8的Stream API:
@Mapper
public interface StreamMapper {
// Stream到List的映射
List<UserDTO> streamToList(Stream<User> users);
// 结合过滤和映射
default List<UserDTO> mapActiveUsers(Stream<User> users) {
return users
.filter(user -> Boolean.TRUE.equals(user.getActive()))
.map(this::entityToDto)
.collect(Collectors.toList());
}
UserDTO entityToDto(User user);
}
4.13 Map映射支持
MapStruct提供强大的Map映射功能:
@Mapper
public interface MapMapper {
// Map到Bean的映射
@Mapping(source = "firstName", target = "name")
@Mapping(source = "userAge", target = "age")
UserDTO mapToDto(Map<String, Object> userMap);
// Bean到Map的映射
@Mapping(source = "name", target = "firstName")
@Mapping(source = "age", target = "userAge")
Map<String, Object> dtoToMap(UserDTO dto);
// Map键值转换
@MapMapping(keyDateFormat = "yyyy-MM-dd", valueDateFormat = "yyyy-MM-dd HH:mm:ss")
Map<String, ZonedDateTime> transformMap(Map<String, LocalDate> source);
}
4.14 子类映射(Subclass Mapping)
MapStruct支持处理继承层次结构的映射。以下是一个基于用户角色的示例:
// 基础用户类
public abstract class BaseUser {
private Long id;
private String username;
private String email;
}
// 管理员用户
public class AdminUser extends BaseUser {
private String adminLevel;
private List<String> permissions;
}
// 普通用户
public class RegularUser extends BaseUser {
private String department;
private String position;
}
// 对应的DTO
public abstract class BaseUserDTO {
private Long id;
private String username;
private String email;
}
public class AdminUserDTO extends BaseUserDTO {
private String adminLevel;
private List<String> permissions;
}
public class RegularUserDTO extends BaseUserDTO {
private String department;
private String position;
}
@Mapper(componentModel = "spring")
public interface UserSubclassMapper {
@SubclassMapping(source = AdminUser.class, target = AdminUserDTO.class)
@SubclassMapping(source = RegularUser.class, target = RegularUserDTO.class)
BaseUserDTO map(BaseUser user);
AdminUserDTO adminToDto(AdminUser admin);
RegularUserDTO regularToDto(RegularUser regular);
}
注意: 这是一个概念性示例,实际项目中可根据业务需求定义相应的继承结构。
4.15 条件映射的高级用法
基于官方条件映射文档:
@Mapper
public interface ConditionalMapper {
// 基于源值的条件映射
@Mapping(target = "name", source = "firstName",
conditionExpression = "java(source.getFirstName() != null && source.getFirstName().length() > 2)")
UserDTO mapConditionally(User source);
// 基于目标对象状态的条件映射
@Condition
default boolean isNotEmpty(String value) {
return value != null && !value.trim().isEmpty();
}
@Mapping(target = "fullName", source = "firstName", conditionQualifiedByName = "isNotEmpty")
UserSimpleDTO mapUserConditionally(User user);
@Named("isNotEmpty")
@Condition
default boolean checkNotEmpty(String value) {
return value != null && !value.trim().isEmpty();
}
}
4.16 异常处理策略
@Mapper
public interface SafeMapper {
// 忽略映射异常
@BeanMapping(ignoreUnmappedSourceProperties = {"internalField", "temporaryData"})
UserDTO safeMapping(User user);
// 自定义异常处理
default String safeConvert(Object value) {
try {
return value != null ? value.toString() : null;
} catch (Exception e) {
System.err.println("转换失败: " + e.getMessage());
return "CONVERSION_ERROR";
}
}
@Mapping(target = "fullName", source = "username", qualifiedByName = "safeConvert")
UserSimpleDTO mapSafely(User user);
}