MapStruct应用指南

MapStruct应用指南

1. MapStruct简介与核心价值

MapStruct 是一个开源的Java注解处理器,用于生成类型安全、高性能且无依赖的Bean映射代码。它在编译时生成映射实现,避免了运行时反射带来的性能损耗。

官方网站: https://mapstruct.org/
官方文档: https://mapstruct.org/documentation/stable/reference/html/

1.1 MapStruct的核心优势

  1. 编译时代码生成 - 零运行时性能损耗
  2. 类型安全 - 编译时检查类型匹配
  3. 易于调试 - 生成的代码清晰可读
  4. 无外部依赖 - 仅在编译时需要
  5. Spring集成 - 无缝集成Spring Framework

2. MapStruct核心注解完整参考

2.1 注解总览表

注解类别注解名称主要用途使用位置
基础注解@Mapper标记映射器接口接口
@Mapping配置字段映射规则方法
方法注解@Named命名自定义方法方法
@BeforeMapping映射前执行方法
@AfterMapping映射后执行方法
枚举注解@ValueMapping枚举值映射方法
@EnumMapping枚举策略配置方法
行为控制@BeanMappingBean级别映射控制方法
@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 完整参数表:

参数类型默认值说明示例
componentModelString“default”组件模型类型“spring”, “cdi”, “jsr330”
usesClass[]{}依赖的映射器或工具类{DateMapper.class}
importsClass[]{}表达式中使用的类型{LocalDateTime.class}
unmappedTargetPolicyReportingPolicyIGNORE未映射目标字段策略IGNORE, WARN, ERROR
unmappedSourcePolicyReportingPolicyIGNORE未映射源字段策略IGNORE, WARN, ERROR
nullValueMappingStrategyNullValueMappingStrategyRETURN_NULLnull值映射策略RETURN_NULL, RETURN_DEFAULT
nullValuePropertyMappingStrategyNullValuePropertyMappingStrategySET_TO_NULLnull属性映射策略SET_TO_NULL, IGNORE, SET_TO_DEFAULT
nullValueCheckStrategyNullValueCheckStrategyON_IMPLICIT_CONVERSIONnull值检查策略ON_IMPLICIT_CONVERSION, ALWAYS

componentModel 详解:

说明使用方式适用场景
"default"默认模式,无依赖注入MyMapper.INSTANCE.map(obj)简单项目,无框架依赖
"spring"Spring Bean模式@Autowired MyMapper mapperSpring Boot项目(推荐)
"cdi"CDI Bean模式@Inject MyMapper mapperJava 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 完整参数表:

参数类型说明示例
sourceString源字段名source = "firstName"
targetString目标字段名target = "fullName"
expressionStringJava表达式expression = "java(source.getAge() + 1)"
constantString常量值constant = "DEFAULT_VALUE"
defaultValueString默认值defaultValue = "0"
ignoreboolean是否忽略ignore = true
qualifiedByNameString自定义方法名qualifiedByName = "formatDate"
conditionExpressionString条件表达式conditionExpression = "java(source != null)"
dependsOnString[]依赖字段dependsOn = {"firstName", "lastName"}
nullValuePropertyMappingStrategyNullValuePropertyMappingStrategynull值策略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 参数表:

参数类型说明示例
sourceString源枚举值source = "ACTIVE"
targetString目标枚举值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 参数表:

参数类型说明可选值
nullValueMappingStrategyNullValueMappingStrategynull对象映射策略RETURN_NULL, RETURN_DEFAULT
nullValuePropertyMappingStrategyNullValuePropertyMappingStrategynull属性映射策略SET_TO_NULL, IGNORE, SET_TO_DEFAULT
nullValueCheckStrategyNullValueCheckStrategynull检查策略ON_IMPLICIT_CONVERSION, ALWAYS
ignoreUnmappedSourcePropertiesString[]忽略的源属性{“password”, “internal”}
ignoreByDefaultboolean默认忽略所有字段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 测试方法

  1. 编译项目: mvn clean compile
  2. 启动应用: mvn spring-boot:run
  3. 测试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.ACTIVEStatusCode.OK
  • UserStatus.INACTIVEStatusCode.PENDING
  • UserStatus.SUSPENDEDStatusCode.BLOCKED
  • UserStatus.DELETEDStatusCode.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 性能优化与实用技巧

  1. 使用批量映射:避免在循环中逐个调用映射方法
  2. 合理使用表达式:复杂逻辑使用自定义方法而非表达式
  3. 避免过度映射:根据实际需要选择简单DTO或完整DTO
  4. 利用Spring Bean:使用componentModel="spring"避免重复实例化
  5. 条件映射:根据业务条件决定映射逻辑
  6. 多源映射:从多个参数构建复杂对象

通过这些高级技巧,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);
}
注:下文中的 *** 代表文件名中的组件名称。 # 包含: 中文-英文对照文档:【***-javadoc-API文档-中文(简体)-英语-对照版.zip】 jar包下载地址:【***.jar下载地址(官方地址+国内镜像地址).txt】 Maven依赖:【***.jar Maven依赖信息(可用于项目pom.xml).txt】 Gradle依赖:【***.jar Gradle依赖信息(可用于项目build.gradle).txt】 源代码下载地址:【***-sources.jar下载地址(官方地址+国内镜像地址).txt】 # 本文件关键字: 中文-英文对照文档,中英对照文档,java,jar包,Maven,第三方jar包,组件,开源组件,第三方组件,Gradle,中文API文档,手册,开发手册,使用手册,参考手册 # 使用方法: 解压 【***.jar中文文档.zip】,再解压其中的 【***-javadoc-API文档-中文(简体)版.zip】,双击 【index.html】 文件,即可用浏览器打开、进行查看。 # 特殊说明: ·本文档为人性化翻译,精心制作,请放心使用。 ·本文档为双语同时展示,一行原文、一行译文,可逐行对照,避免了原文/译文来回切换的麻烦; ·有原文可参照,不再担心翻译偏差误导; ·边学技术、边学英语。 ·只翻译了该翻译的内容,如:注释、说明、描述、用法讲解 等; ·不该翻译的内容保持原样,如:类名、方法名、包名、类型、关键字、代码 等。 # 温馨提示: (1)为了防止解压后路径太长导致浏览器无法打开,推荐在解压时选择“解压到当前文件夹”(放心,自带文件夹,文件不会散落一地); (2)有时,一套Java组件会有多个jar,所以在下载前,请仔细阅读本篇描述,以确保这就是你需要的文件;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值