李锋镝的博客

  • 首页
  • 时间轴
  • 说说
  • 左邻右舍
  • 博友圈
  • 关于我
    • 关于我
    • 另一个网站
    • 我的导航站
    • 网站地图
    • 赞助
  • 留言
  • 走心评论
  • 系列文章
  • Now
  • 每日心情
  • 🚇开往
Destiny
自是人生长恨水长东
  1. 首页
  2. 后端
  3. 正文

MapStruct深度解析:从原理到实战,告别BeanUtil的性能与安全痛点

2025年10月22日 约 4,714 字16 分钟 24点热度 0人点赞 0条评论
本文最后更新于 2025年10月22日,距今已 290 天,其中的信息可能已经发生变化,请注意甄别。

在Java开发中,对象映射是高频操作——从DTO转实体、实体转VO,再到多数据源合并为目标对象,几乎每个业务层都离不开。传统的BeanUtil.copyProperties虽简单,却存在反射开销大、类型不安全、字段映射不可控等问题。而MapStruct作为编译时生成映射代码的框架,完美解决了这些痛点,成为企业级项目的首选方案。本文将从核心原理、核心注解、实战案例到常见问题,全方位拆解MapStruct的使用。

一、MapStruct核心认知:为什么它比BeanUtil更优?

在学习使用前,首先要明确MapStruct的本质与优势,理解它为何能替代传统映射工具。

1.1 什么是MapStruct?

MapStruct是一款Java注解驱动的代码生成器,专注于简化Java Bean之间的映射实现。它通过在编译期分析映射接口与注解,自动生成纯Java代码的映射实现类,而非运行时通过反射完成映射。

简单来说:MapStruct不做“运行时魔法”,而是在项目编译时就帮你写好userToUserDTO这类映射方法,你直接调用即可。

1.2 核心优势:对比BeanUtil的碾压性优势

传统工具(如Apache BeanUtils、Spring BeanUtils)与MapStruct的差异,本质是“反射”与“编译期代码生成”的差异,具体体现在4个维度:

对比维度 传统BeanUtil(反射实现) MapStruct(编译期生成代码)
性能 反射需动态解析类结构,开销大(比MapStruct慢10-100倍) 纯Java代码调用(get/set),无额外开销
类型安全 运行时才会发现类型不匹配(如String转Integer) 编译期报错,提前规避风险
灵活性 仅支持字段名完全匹配,自定义映射需额外代码 支持字段别名、类型转换、多数据源映射等
可调试性 反射逻辑黑盒,无法断点调试 生成的代码清晰可读,可直接断点排查

示例对比:同样是User转UserDTO,MapStruct生成的代码如下(编译后可见):

// MapStruct自动生成的实现类
public class UserMapperImpl implements UserMapper {
    @Override
    public UserDTO userToUserDTO(User user) {
        if (user == null) {
            return null;
        }
        UserDTO userDTO = new UserDTO();
        userDTO.setId(user.getId()); // 直接调用get/set
        userDTO.setUsername(user.getUsername());
        userDTO.setEmail(user.getEmail());
        userDTO.setCreateTime(user.getCreateTime().toString()); // 若配置了类型转换
        userDTO.setAddressDTO(addressToAddressDTO(user.getAddress())); // 关联对象映射
        return userDTO;
    }
}

这种纯Java代码的映射方式,性能与手写代码完全一致,且无需担心反射带来的问题。

1.3 工作原理:编译期生成代码的3个步骤

MapStruct的核心是“注解处理器(Annotation Processor)”,它在Maven/Gradle编译项目时触发,完成3个关键步骤:

  1. 解析注解:扫描项目中带@Mapper注解的接口,分析接口中的映射方法(如userToUserDTO)、@Mapping等注解配置;
  2. 生成映射代码:根据注解配置,生成接口的实现类(如UserMapperImpl),实现类中包含字段映射、类型转换、关联对象处理等逻辑;
  3. 编译加载:生成的实现类与项目其他代码一起编译为class文件,运行时可通过接口直接调用(支持Spring依赖注入)。

二、环境搭建:5分钟集成MapStruct

MapStruct的集成需依赖核心包与注解处理器,支持Maven和Gradle,以下以Maven为例。

2.1 Maven依赖配置

需在pom.xml中添加3部分配置:核心依赖、注解处理器、Lombok兼容(若项目使用Lombok)。

<properties>
    <!-- MapStruct版本(建议使用1.6.x及以上,兼容Java 8+) -->
    <org.mapstruct.version>1.6.3</org.mapstruct.version>
</properties>

<dependencies>
    <!-- 1. MapStruct核心依赖(接口与基础类) -->
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>${org.mapstruct.version}</version>
    </dependency>
</dependencies>

<build>
    <plugins>
        <!-- 2. Maven编译插件:配置注解处理器 -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>1.8</source> <!-- 项目Java版本 -->
                <target>1.8</target>
                <annotationProcessorPaths>
                    <!-- 3. MapStruct注解处理器(关键:编译时生成代码) -->
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${org.mapstruct.version}</version>
                    </path>
                    <!-- 4. Lombok兼容:若项目用Lombok,需添加此绑定包 -->
                    <path>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok-mapstruct-binding</artifactId>
                        <version>0.2.0</version>
                    </path>
                    <!-- 5. Lombok注解处理器(若项目用Lombok) -->
                    <path>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok</artifactId>
                        <version>1.18.30</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

2.2 关键配置说明

  • 注解处理器路径:annotationProcessorPaths必须包含mapstruct-processor,否则无法生成映射代码;
  • Lombok兼容:Lombok 1.18.16+与MapStruct存在编译顺序冲突,需添加lombok-mapstruct-binding协调两者的注解处理顺序;
  • Java版本:MapStruct 1.6.x支持Java 8-17,若使用Java 11+,需确保maven-compiler-plugin版本≥3.8.1。

三、核心注解:掌握90%场景的关键配置

MapStruct通过注解定义映射规则,核心注解仅6个,覆盖字段映射、类型转换、生命周期等场景。

3.1 @Mapper:标记映射接口(入口注解)

@Mapper是MapStruct的核心注解,用于标记“映射接口/抽象类”,告诉注解处理器需要为该接口生成实现类。

常用属性:

  • componentModel:指定映射器的组件模型,控制实现类的实例化方式(如Spring依赖注入);
    • default:默认值,通过Mappers.getMapper(Class)获取实例(无依赖注入);
    • spring:生成@Component修饰的实现类,支持@Autowired注入;
    • cdi:支持CDI容器(Java EE);
  • uses:指定当前映射器依赖的其他映射器(如UserMapper依赖AddressMapper);
  • unmappedTargetPolicy:未映射目标字段的处理策略(如ReportingPolicy.ERROR表示未映射时编译报错)。

示例:Spring环境下的映射接口

// Spring环境:生成@Component,可@Autowired注入
@Mapper(componentModel = "spring", uses = AddressMapper.class)
public interface UserMapper {
    // 映射方法:User → UserDTO
    UserDTO userToUserDTO(User user);

    // 映射方法:UserDTO → User
    User userDTOToUser(UserDTO userDTO);

    // 集合映射:List<User> → List<UserDTO>(无需手动实现)
    List<UserDTO> userListToUserDTOList(List<User> userList);
}

3.2 @Mapping:字段级映射规则

当源对象与目标对象的字段名不匹配、类型不同,或需要自定义映射逻辑时,用@Mapping注解配置单个字段的规则。

常用属性:

  • source:源对象的字段名(如user.getCreateTime());
  • target:目标对象的字段名(如userDTO.setCreateDate());
  • dateFormat:日期类型转换格式(如"yyyy-MM-dd HH:mm:ss",适用于Date/String互转);
  • numberFormat:数字类型转换格式(如"#.00",适用于BigDecimal/String互转);
  • qualifiedByName:指定自定义转换方法(配合@Named注解);
  • expression:通过Java表达式自定义映射逻辑(如拼接字符串);
  • defaultValue:源字段为null时的默认值(如"未知");
  • constant:直接给目标字段设置常量(如"USER")。

示例:多场景字段映射

@Mapper(componentModel = "spring")
public interface UserMapper {
    // 1. 字段名不匹配:source="createTime" → target="createDate"
    // 2. 日期格式转换:LocalDateTime → String(格式:yyyy-MM-dd HH:mm:ss)
    // 3. 源字段为null时,target默认值为"未知用户"
    @Mapping(source = "createTime", target = "createDate", dateFormat = "yyyy-MM-dd HH:mm:ss")
    @Mapping(source = "username", target = "nickname", defaultValue = "未知用户")
    @Mapping(target = "userType", constant = "COMMON") // 常量赋值
    @Mapping(target = "fullName", expression = "java(source.getFirstName() + \" \" + source.getLastName())") // 表达式拼接
    UserDTO userToUserDTO(User user);
}

3.3 @MappingTarget:更新已有对象(非新建)

默认情况下,MapStruct会新建目标对象(如new UserDTO()),若需“更新已有对象”(如从DTO更新实体的部分字段),用@MappingTarget标记目标对象参数。

示例:更新用户信息

@Mapper(componentModel = "spring")
public interface UserMapper {
    /**
     * 用UserUpdateDTO更新User对象(非新建User)
     * @param dto 源对象(更新数据来源)
     * @param user 目标对象(待更新的已有对象)
     */
    @Mapping(source = "email", target = "email") // 仅更新email字段
    @Mapping(source = "phone", target = "phone") // 仅更新phone字段
    void updateUserFromDTO(UserUpdateDTO dto, @MappingTarget User user);
}

// 调用方式(Service层)
@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public void updateUser(Long userId, UserUpdateDTO dto) {
        User user = userRepository.findById(userId).orElseThrow();
        userMapper.updateUserFromDTO(dto, user); // 直接更新已有user对象
        userRepository.save(user);
    }
}

3.4 @ValueMapping:枚举类型映射

当源枚举与目标枚举的常量名不匹配时(如PaymentStatus.PENDING → PaymentStatusDTO.INIT),用@ValueMapping配置枚举映射规则。

示例:枚举映射

// 源枚举(数据库实体用)
public enum PaymentStatus {
    PENDING,  // 待支付
    PROCESSING, // 处理中
    COMPLETED, // 已完成
    FAILED // 失败
}

// 目标枚举(DTO用)
public enum PaymentStatusDTO {
    INIT,     // 初始化(对应PENDING)
    IN_PROGRESS, // 处理中(对应PROCESSING)
    SUCCESS,  // 成功(对应COMPLETED)
    ERROR     // 错误(对应FAILED)
}

// 枚举映射器
@Mapper(componentModel = "spring")
public interface PaymentStatusMapper {
    @ValueMappings({
        @ValueMapping(source = "PENDING", target = "INIT"),
        @ValueMapping(source = "PROCESSING", target = "IN_PROGRESS"),
        @ValueMapping(source = "COMPLETED", target = "SUCCESS"),
        @ValueMapping(source = "FAILED", target = "ERROR"),
        // 源为null时,默认映射为INIT
        @ValueMapping(source = MappingConstants.NULL, target = "INIT"),
        // 未匹配的其他枚举值,默认映射为ERROR
        @ValueMapping(source = MappingConstants.ANY_REMAINING, target = "ERROR")
    })
    PaymentStatusDTO toDTO(PaymentStatus status);
}

3.5 @BeforeMapping/@AfterMapping:映射生命周期

在映射开始前(@BeforeMapping)或结束后(@AfterMapping)执行自定义逻辑,如参数校验、字段补全(如lastUpdateTime)。

示例:映射前后的校验与补全

@Mapper(componentModel = "spring")
public abstract class UserMapper { // 注意:用抽象类而非接口,支持自定义方法
    // 映射前校验:确保源对象非空、邮箱非空
    @BeforeMapping
    protected void validateUserDTO(UserDTO source) {
        if (source == null) {
            throw new IllegalArgumentException("UserDTO cannot be null");
        }
        if (source.getEmail() == null || source.getEmail().isEmpty()) {
            throw new IllegalArgumentException("Email is required");
        }
    }

    // 映射后补全:自动设置更新时间和版本号
    @AfterMapping
    protected void enrichUserEntity(@MappingTarget User target) {
        target.setLastUpdateTime(LocalDateTime.now());
        target.setVersion(target.getVersion() + 1); // 乐观锁版本号自增
    }

    // 抽象映射方法(MapStruct会生成实现)
    @Mapping(source = "userId", target = "id")
    public abstract User toEntity(UserDTO source);
}

3.6 @BeanMapping:对象级映射配置

@BeanMapping用于配置“整个对象”的映射规则(而非单个字段),如null值处理策略、是否忽略默认映射等。

常用属性:

  • nullValueMappingStrategy:源对象为null时的策略(如RETURN_DEFAULT返回目标对象的默认实例);
  • nullValuePropertyMappingStrategy:源字段为null时的策略(如IGNORE不更新目标字段);
  • ignoreByDefault:是否默认忽略所有字段映射(需手动配置@Mapping才生效);
  • ignoreUnmappedSourceProperties:忽略源对象中未映射的字段(避免编译警告)。

示例:忽略null值更新

@Mapper(componentModel = "spring")
public interface AddressMapper {
    /**
     * 更新地址:源字段为null时,不更新目标字段(保留原有值)
     */
    @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
    @Mapping(source = "street", target = "street")
    @Mapping(source = "city", target = "city")
    void updateAddress(AddressDTO dto, @MappingTarget Address address);
}

四、实战案例:覆盖80%业务场景

结合实际业务场景,演示MapStruct在“基础映射、类型转换、多数据源合并、集合映射”等场景的使用。

4.1 场景1:基础DTO-实体映射(含关联对象)

需求:将User实体(含Address关联对象)转换为UserDTO(含AddressDTO关联对象),字段名部分不匹配。

步骤1:定义实体与DTO

// 1. 关联实体:Address
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Address {
    private Long id;
    private String street; // 街道(如"123 Main St")
    private String city;   // 城市(如"Beijing")
    private String postalCode; // 邮编(如"100000")
}

// 2. 主实体:User
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Long id;
    private String username; // 用户名
    private String email;    // 邮箱
    private LocalDateTime createTime; // 创建时间
    private Address address; // 关联地址
}

// 3. 关联DTO:AddressDTO
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AddressDTO {
    private Long id;
    private String street;
    private String city;
    private String postalCode;
}

// 4. 主DTO:UserDTO
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserDTO {
    private Long id;
    private String nickname; // 对应User.username(字段名不匹配)
    private String email;
    private String createDate; // 对应User.createTime(类型不匹配:LocalDateTime→String)
    private AddressDTO addressDTO; // 对应User.address(关联对象映射)
}

步骤2:定义映射器

// 1. 关联对象映射器:AddressMapper
@Mapper(componentModel = "spring")
public interface AddressMapper {
    AddressDTO addressToDTO(Address address);
    Address dtoToAddress(AddressDTO dto);
}

// 2. 主映射器:UserMapper(依赖AddressMapper)
@Mapper(
    componentModel = "spring",
    uses = AddressMapper.class, // 依赖AddressMapper处理关联对象
    unmappedTargetPolicy = ReportingPolicy.IGNORE // 忽略未映射的字段(避免警告)
)
public interface UserMapper {
    @Mapping(source = "username", target = "nickname") // 字段名不匹配
    @Mapping(source = "createTime", target = "createDate", dateFormat = "yyyy-MM-dd HH:mm:ss") // 日期类型转换
    @Mapping(source = "address", target = "addressDTO") // 关联对象映射(依赖AddressMapper)
    UserDTO userToDTO(User user);

    // 反向映射:DTO→实体
    @Mapping(source = "nickname", target = "username")
    @Mapping(source = "createDate", target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
    @Mapping(source = "addressDTO", target = "address")
    User dtoToUser(UserDTO dto);
}

步骤3:调用映射器(Service层)

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private UserRepository userRepository;

    // 获取用户详情(实体→DTO)
    public UserDTO getUserDetail(Long userId) {
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new RuntimeException("用户不存在"));
        return userMapper.userToDTO(user); // 直接调用映射方法
    }

    // 创建用户(DTO→实体)
    public void createUser(UserDTO userDTO) {
        User user = userMapper.dtoToUser(userDTO);
        user.setCreateTime(LocalDateTime.now()); // 补充默认值
        userRepository.save(user);
    }
}

4.2 场景2:多数据源合并映射

需求:将UserBasicInfo(基础信息)和UserExtInfo(扩展信息)两个对象,合并为UserDetailVO(详情VO)。

步骤1:定义数据源与目标VO

// 数据源1:基础信息
@Data
public class UserBasicInfo {
    private Long userId;
    private String username;
    private String email;
}

// 数据源2:扩展信息
@Data
public class UserExtInfo {
    private Long userId;
    private Integer age;
    private String phone;
    private String address;
}

// 目标VO:用户详情
@Data
public class UserDetailVO {
    private Long id;
    private String username;
    private String email;
    private Integer age;
    private String phone;
    private String address;
}

步骤2:定义多数据源映射器

@Mapper(componentModel = "spring")
public interface UserDetailMapper {
    /**
     * 多数据源合并:BasicInfo + ExtInfo → DetailVO
     * 注:当多个数据源有同名字段(如userId),需用source指定数据源参数名
     */
    @Mapping(source = "basic.userId", target = "id") // 明确指定从basic获取userId
    @Mapping(source = "basic.username", target = "username")
    @Mapping(source = "basic.email", target = "email")
    @Mapping(source = "ext.age", target = "age") // 从ext获取age
    @Mapping(source = "ext.phone", target = "phone")
    @Mapping(source = "ext.address", target = "address")
    UserDetailVO mergeToDetailVO(UserBasicInfo basic, UserExtInfo ext);
}

步骤3:调用合并映射

@Service
public class UserDetailService {
    @Autowired
    private UserBasicMapper basicMapper; // 假设从DB查询基础信息
    @Autowired
    private UserExtMapper extMapper;     // 假设从Redis查询扩展信息
    @Autowired
    private UserDetailMapper detailMapper;

    public UserDetailVO getUserDetail(Long userId) {
        // 1. 获取两个数据源
        UserBasicInfo basic = basicMapper.getByUserId(userId);
        UserExtInfo ext = extMapper.getByUserId(userId);
        // 2. 合并为VO
        return detailMapper.mergeToDetailVO(basic, ext);
    }
}

4.3 场景3:自定义类型转换(如String转枚举)

需求:将前端传入的字符串状态(如"A")转换为后端枚举(如UserStatus.ACTIVE),并映射到User实体。

步骤1:定义枚举与DTO

// 枚举:用户状态
public enum UserStatus {
    ACTIVE("A", "激活"),
    INACTIVE("I", "未激活"),
    LOCKED("L", "锁定");

    private final String code;
    private final String desc;

    // 构造器、getter省略
    public static UserStatus getByCode(String code) {
        for (UserStatus status : values()) {
            if (status.code.equals(code)) {
                return status;
            }
        }
        throw new IllegalArgumentException("无效状态码:" + code);
    }
}

// DTO:前端传入的创建参数
@Data
public class UserCreateDTO {
    private String username;
    private String email;
    private String statusCode; // 前端传入状态码(如"A")
}

// 实体:User
@Data
public class User {
    private Long id;
    private String username;
    private String email;
    private UserStatus status; // 后端枚举
}

步骤2:自定义转换方法+映射器

@Mapper(componentModel = "spring")
public interface UserCreateMapper {
    // 1. 自定义转换方法:String(code)→ UserStatus
    default UserStatus stringToUserStatus(String code) {
        if (code == null || code.isEmpty()) {
            return UserStatus.INACTIVE; // 默认未激活
        }
        return UserStatus.getByCode(code);
    }

    // 2. 映射方法:DTO→实体(使用自定义转换)
    @Mapping(source = "statusCode", target = "status", qualifiedByName = "stringToUserStatus")
    @Named("stringToUserStatus") // 与qualifiedByName对应
    User toEntity(UserCreateDTO dto);
}

4.4 场景4:集合映射(List/Set映射)

需求:将List<User>转换为List<UserDTO>,MapStruct自动支持集合映射,无需手动循环。

示例:集合映射器

@Mapper(componentModel = "spring", uses = AddressMapper.class)
public interface UserListMapper {
    // 1. 单个对象映射(基础)
    @Mapping(source = "username", target = "nickname")
    @Mapping(source = "createTime", target = "createDate", dateFormat = "yyyy-MM-dd")
    UserDTO userToDTO(User user);

    // 2. List映射(MapStruct自动生成循环逻辑)
    List<UserDTO> userListToDTOList(List<User> userList);

    // 3. Set映射(同理)
    Set<UserDTO> userSetToDTOSet(Set<User> userSet);
}

// 调用示例
@Service
public class UserListService {
    @Autowired
    private UserListMapper listMapper;
    @Autowired
    private UserRepository userRepository;

    public List<UserDTO> getUserList() {
        List<User> userList = userRepository.findAll();
        return listMapper.userListToDTOList(userList); // 直接映射集合
    }
}

五、常见问题与解决方案

在使用MapStruct时,常会遇到编译报错、Lombok兼容、代码不生成等问题,以下是高频问题的解决方案。

5.1 问题1:映射代码未生成(编译后无Impl类)

原因:

  1. 未配置mapstruct-processor注解处理器;
  2. Maven编译时未触发注解处理器(如用IDE直接运行,未执行mvn compile);
  3. 映射接口未加@Mapper注解。

解决方案:

  1. 检查pom.xml的annotationProcessorPaths是否包含mapstruct-processor;
  2. 执行mvn clean compile手动触发编译,生成Impl类;
  3. 确保映射接口添加了@Mapper注解。

5.2 问题2:Lombok与MapStruct编译冲突(字段无法识别)

现象:使用Lombok的@Data注解时,MapStruct无法识别实体的get/set方法,编译报错“找不到字段的setter”。

原因:Lombok与MapStruct的注解处理器执行顺序冲突,MapStruct先处理时,Lombok尚未生成get/set方法。

解决方案:

  1. 添加lombok-mapstruct-binding依赖(已在环境搭建中配置);
  2. 确保annotationProcessorPaths中,lombok在前,mapstruct-processor在后;
  3. 若仍有问题,在IDE中开启“注解处理”(IntelliJ IDEA:Settings → Build → Compiler → Annotation Processors → 勾选“Enable annotation processing”)。

5.3 问题3:未映射字段编译警告/报错

现象:编译时提示“Unmapped target property: 'xxx'”(未映射目标字段)。

原因:

  1. 目标对象的字段在源对象中无对应字段,且未配置忽略;
  2. unmappedTargetPolicy设置为ReportingPolicy.ERROR(默认是WARNING)。

解决方案:

  1. 若字段无需映射,在@Mapper中配置unmappedTargetPolicy = ReportingPolicy.IGNORE;
  2. 若仅需忽略部分字段,在@BeanMapping中用ignoreUnmappedSourceProperties指定;
  3. 若确实遗漏映射,补充@Mapping注解。

5.4 问题4:类型转换失败(如LocalDateTime转String)

现象:编译报错“Can't map property 'java.time.LocalDateTime createTime' to 'java.lang.String createDate'”。

原因:MapStruct默认不支持LocalDateTime与String的转换,需手动配置格式。

解决方案:

  1. 使用dateFormat属性指定转换格式(适用于Date/LocalDateTime与String互转);
  2. 自定义转换方法(如LocalDateTime.toString())。
// 示例:LocalDateTime→String转换
@Mapping(source = "createTime", target = "createDate", dateFormat = "yyyy-MM-dd HH:mm:ss")
UserDTO userToDTO(User user);

六、总结:MapStruct的最佳实践

  1. 优先使用componentModel = "spring":在Spring项目中,通过依赖注入使用映射器,避免手动获取实例;
  2. 明确字段映射规则:即使字段名匹配,建议关键字段显式配置@Mapping,提高代码可读性;
  3. 拆分复杂映射:多数据源合并、复杂类型转换等场景,拆分为多个小映射器(如AddressMapper、UserMapper),通过uses依赖;
  4. 开启编译期校验:配置unmappedTargetPolicy = ReportingPolicy.ERROR,提前发现未映射字段;
  5. 配合IDE插件:安装IntelliJ IDEA的“MapStruct Support”插件(Plugins → 搜索MapStruct),支持注解补全、代码跳转。
除非注明,否则均为李锋镝的博客原创文章,转载必须以链接形式标明本文链接

本文链接:https://www.lifengdi.com/hou-duan/4534

推荐阅读

  • Spring Boot 配置加载优先级总结
  • 如何通过命令查看Java应用内存中对象数量
  • 记一次Apollo配置中心+Spring配置自动刷新导致的内存泄露问题
  • jasypt-spring-boot 使用说明
  • 配置Jackson使用字段而不是getter/setter来序列化和反序列化
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可
标签: BeanUtils JAVA MapStruct
最后更新:2025年10月22日

岁月同一天 8 月 8 日

回望过去的今天,你在写什么

  • 4 年前 2022年8月8日
    减肥四个月~

    今年四月初开始每天跳绳减肥,到了今天刚好四个月了。从一开始的每天跳绳十分钟到半个小时,再从半个小时到一个小时,最后又稳定…

相关文章
  • Java设计模式:状态模式2025年5月22日
  • 九种常用的UML图总结2019年6月29日
  • 何谓重构?为何重构?2019年6月24日
  • TestNG基本注解2019年7月23日
  • JAVA开发处理金额的数据类型你知道多少?2025年10月28日

李锋镝

既然选择了远方,便只顾风雨兼程。

打赏 点赞
< 上一篇
下一篇 >
1234567891112131415161718192021222324252627282930313233343536373839404142434446474849505152535455575859606162636465666769727476777879808182858687909293949596979899
取消回复
…

文章评论

还没有评论,快来抢沙发吧~

九年没联系的女友突然问我借钱,我开口道:四十万行么?不够了再说话。”前女友很是高兴地回复说:“够用,还是我男朋友有办法啊,你放心,我日后一定还给你,九年多没见了你现在干啥呢,这么有钱?”我呵呵一笑:“没事,不够用您尽管说话,俺现在改做抵押贷款了。”

听点儿音乐吧 朋友~
文章目录
最新 热点 随机
最新 热点 随机
Kratos+ v1.1.14版本更新说明 Spring Boot 指定外部配置文件的方式 Spring Boot 配置加载优先级总结 Claude Fable 5(claude-fable-5)深度详解 如何通过命令查看Java应用内存中对象数量 关于服务的探活端口和业务端口不一致有什么问题
给主题增加了Now、每日心情、年度回顾、岁月同一天、随机漫步等功能AI时代,个人技术博客的出路在哪里?增加了两套复古皮肤-牛皮纸、千禧网页这个域名注册整整十年了,十年时间,真快啊WordPress实现用户评论等级排行榜插件WordPress网站换了个字体,差点儿把样式换崩了
MybatisCodeHelperPro激活 解决kubectl exec -it xxxx-service-bfbd45bb9-ktvzj bash -n bit error: exec [POD] [COMMAND] is not supported anymore. Use exec [POD] -- [COMMAND] instead See 'kubectl exec -h' for help and examples Apollo配置中心中的protalDB的作用是什么 k8s + docker + Jenkins使用Pipeline部署SpringBoot项目时Jenkins错误集锦 彻底搞懂mysql日志系统binlog,redolog,undolog Spring事件驱动深度指南:从单机异步到亿级流量,比MQ更轻的架构神器
最近评论
Huo 发布于 8 小时前(08月07日) 挺有特色的主题,还是感觉 WP 的确是强大
李锋镝 发布于 21 小时前(08月07日) 没理解你想说啥
aboss 发布于 21 小时前(08月07日) 你的后台web-login?
李锋镝 发布于 22 小时前(08月07日) 这个专门的插件实现的功能更好更全,还能对接支付之类的
李锋镝 发布于 22 小时前(08月07日) 自定义登录地址是为了防止大部分机器人通过WP固定登录页面暴力破解用户账号密码
标签聚合
K8s 日常 Claude docker JVM WordPress Redis AI编程 多线程 SQL 架构 MySQL IDEA JAVA SpringBoot Spring AI 数据库 ElasticSearch 分布式
友情链接
  • Blogs·CN
  • 懋和道人
  • 旧时繁华
  • Honesty
  • 拾趣博客导航
  • 搬砖日记
  • 知向前端
  • 临窗旋墨
  • 皮皮社
  • 瓦匠个人小站
  • 志文工作室
  • 韩小韩博客
  • 风渡言
  • 彬红茶日记
  • 老张博客
  • Mr.Sun的博客
  • 韩情脉脉
  • 哥斯拉
  • 林羽凡

COPYRIGHT © 2026 lifengdi.com. ALL RIGHTS RESERVED.

正在博友圈履约中

域名年龄

Theme Kratos+ By Dylan Li

津ICP备2024022503号-3

京公网安备11011502039375号