当前位置: 首页 > news >正文

BeanUtils.copyProperties 映射规则详解

概述

BeanUtils.copyProperties 是 Spring Framework 提供的一个便捷工具,用于在两个 Java 对象之间复制属性值。本文档详细说明其工作原理、映射规则和常见问题。

基本语法

BeanUtils.copyProperties(source, target);
  • source: 源对象(数据来源)
  • target: 目标对象(数据接收方)

核心映射规则

1. 字段名精确匹配

规则: 只有当源对象和目标对象具有完全相同的字段名时,才会进行属性拷贝。

// 源对象
class Source {private String name;     // ✅ 可以映射private Integer age;     // ✅ 可以映射  private Long id;         // ❌ 无法映射到 userId
}// 目标对象
class Target {private String name;     // ✅ 接收 source.nameprivate Integer age;     // ✅ 接收 source.ageprivate Long userId;     // ❌ 无法接收 source.id (字段名不匹配)private String email;    // ❌ source 中没有对应字段,保持为 null
}

2. 数据类型要求

规则: 字段名相同的情况下,数据类型也必须兼容。

// ✅ 兼容的类型映射
StringString
IntegerInteger
LongLong
DateDate
BigDecimalBigDecimal// ❌ 不兼容的类型映射
StringInteger  // 会导致异常
LongString     // 会导致异常

3. Getter/Setter 方法要求

规则: 源对象必须有对应的 getter 方法,目标对象必须有对应的 setter 方法。

// 源对象需要 getter
public String getName() { return name; }// 目标对象需要 setter  
public void setName(String name) { this.name = name; }

实际案例分析

案例背景

在听书项目中,需要将 TrackInfo 实体转换为 TrackListVo 视图对象:

// TrackInfo 实体类 (继承 BaseEntity)
@Data
public class TrackInfo extends BaseEntity {private Long userId;private Long albumId;private String trackTitle;private String coverUrl;private BigDecimal mediaDuration;private String status;// ... 其他字段
}// BaseEntity 基类
@Data  
public class BaseEntity {private Long id;           // 关键字段!private Date createTime;private Date updateTime;// ...
}// TrackListVo 视图对象
@Data
public class TrackListVo {private Long albumId;private String albumTitle;private Long trackId;      // 关键字段!private String trackTitle;private String coverUrl;private BigDecimal mediaDuration;private String status;private Integer playStatNum;private Integer collectStatNum;// ...
}

映射结果分析

TrackInfo trackInfo = getTrackInfoFromDB();
TrackListVo trackListVo = new TrackListVo();BeanUtils.copyProperties(trackInfo, trackListVo);

成功映射的字段:

字段名TrackInfoTrackListVo结果
albumId✅ 成功复制
trackTitle✅ 成功复制
coverUrl✅ 成功复制
mediaDuration✅ 成功复制
status✅ 成功复制

无法映射的字段:

字段名原因结果
id → trackId字段名不匹配trackId 为 null
albumTitleTrackInfo 中不存在albumTitle 为 null
playStatNumTrackInfo 中不存在playStatNum 为 null
collectStatNumTrackInfo 中不存在collectStatNum 为 null

问题和解决方案

问题: trackId 字段为 null,导致后续使用时出现 NullPointerException。

// 错误代码 - trackId 为 null
TrackListVo trackListVo = trackListVoMap.get(trackId);
userCollectVo.setAlbumId(trackListVo.getAlbumId()); // NPE!

解决方案: 手动设置无法自动映射的关键字段。

// 正确的做法
return trackInfoList.stream().map(trackInfo -> {TrackListVo trackListVo = new TrackListVo();BeanUtils.copyProperties(trackInfo, trackListVo);// 手动设置关键字段trackListVo.setTrackId(trackInfo.getId()); // 解决 id → trackId 映射return trackListVo;}).collect(Collectors.toList());

最佳实践

1. 空值检查

public List<TrackListVo> convertToTrackListVo(List<TrackInfo> trackInfoList) {if (CollectionUtils.isEmpty(trackInfoList)) {return new ArrayList<>();}return trackInfoList.stream().filter(Objects::nonNull) // 过滤 null 对象.map(trackInfo -> {TrackListVo trackListVo = new TrackListVo();BeanUtils.copyProperties(trackInfo, trackListVo);// 手动设置特殊字段if (trackInfo.getId() != null) {trackListVo.setTrackId(trackInfo.getId());}return trackListVo;}).collect(Collectors.toList());
}

2. 字段映射文档化

/*** TrackInfo → TrackListVo 字段映射说明:* * 自动映射字段:* - albumId → albumId* - trackTitle → trackTitle  * - coverUrl → coverUrl* - mediaDuration → mediaDuration* - status → status* * 手动映射字段:* - id → trackId (字段名不匹配)* * 无映射字段:* - albumTitle (需要从其他服务获取)* - playStatNum (统计数据,需要单独查询)* - collectStatNum (统计数据,需要单独查询)*/

3. 单元测试验证

@Test
public void testTrackInfoToTrackListVoMapping() {// GivenTrackInfo trackInfo = new TrackInfo();trackInfo.setId(1L);trackInfo.setAlbumId(100L);trackInfo.setTrackTitle("测试音频");trackInfo.setCoverUrl("http://example.com/cover.jpg");// WhenTrackListVo result = convertToTrackListVo(trackInfo);// ThenassertThat(result.getTrackId()).isEqualTo(1L);        // 手动映射assertThat(result.getAlbumId()).isEqualTo(100L);      // 自动映射assertThat(result.getTrackTitle()).isEqualTo("测试音频"); // 自动映射assertThat(result.getCoverUrl()).isEqualTo("http://example.com/cover.jpg"); // 自动映射
}

常见错误和调试

1. NullPointerException

原因: 关键字段映射失败,导致后续使用时为 null。

调试方法:

// 添加调试日志
log.debug("TrackInfo: id={}, albumId={}", trackInfo.getId(), trackInfo.getAlbumId());
log.debug("TrackListVo: trackId={}, albumId={}", trackListVo.getTrackId(), trackListVo.getAlbumId());

2. 类型转换异常

原因: 字段名相同但类型不兼容。

解决: 检查字段类型定义,确保兼容性。

3. 映射不生效

原因: 缺少 getter/setter 方法,或字段名拼写错误。

解决:

  • 确保使用 @Data 注解或手动添加 getter/setter
  • 检查字段名拼写是否完全一致(包括大小写)

替代方案

1. MapStruct (推荐)

@Mapper
public interface TrackMapper {@Mapping(source = "id", target = "trackId")TrackListVo toTrackListVo(TrackInfo trackInfo);
}

2. 手动映射

public TrackListVo toTrackListVo(TrackInfo trackInfo) {TrackListVo vo = new TrackListVo();vo.setTrackId(trackInfo.getId());vo.setAlbumId(trackInfo.getAlbumId());vo.setTrackTitle(trackInfo.getTrackTitle());// ... 其他字段return vo;
}

总结

  • BeanUtils.copyProperties 只能处理字段名完全相同的属性映射
  • 对于字段名不匹配的情况,必须手动设置
  • 在生产环境中,建议添加空值检查和异常处理
  • 考虑使用 MapStruct 等更强大的映射工具来替代 BeanUtils
http://www.dtcms.com/a/394564.html

相关文章:

  • 物联网 frid卡控制
  • LeetCode刷题记录----322.零钱兑换(Medium)
  • 2015/07 JLPT听力原文 问题四
  • Redis集群实验
  • 昇腾生态双支柱:MindSpore 与 CANN 的全栈技术解析
  • YOLO系列——实时屏幕检测
  • 牛客算法基础noob49 上三角矩阵判定
  • autosar 中OS模块理解
  • 通俗范畴论17.2 向量空间的对偶与双对偶
  • huggingface_hub 安装部署问题汇总
  • 在我的Java项目中为什么使用AllArgsConstructor注解注入的方式启动报错了:
  • π0:一个 VLA 流匹配模型用于通用机器人控制(又称 pi0)
  • Information theorem-Entropy
  • 编译原理实验报告——词法分析程序
  • 整体设计 完整的逻辑链条 之4 认知逻辑视角 —— 前序驱动的认知演进体系 之2
  • C/C++正则表达式PCRE2库
  • 基于python大数据的声乐信息分类评测系统
  • 永磁同步电机无速度算法--改进型超螺旋滑模观测器
  • Linux0.12的中断处理过程源码分析
  • 进程控制(Linux)
  • 【C++】——string类的使用(详细讲解)
  • 借助 Amazon ECS 全新的内置蓝绿部署功能,加速安全的软件发布进程
  • 【脑电分析系列】第24篇:运动想象BCI系统构建:CSP+LDA/SVM与深度学习方法的对比研究
  • 【论文速递】2025年第22周(May-25-31)(Robotics/Embodied AI/LLM)
  • MySQL 5.7 多实例部署完整指南(基于二进制包)
  • Git的使用——Git命令、密钥/私钥、文件推送/提交、分支增删改查、文件回滚、.gitignore文件忽略
  • [已更新]2025华为杯D题数学建模研赛D题研究生数学建模思路代码文章成品:低空湍流监测及最优航路规划
  • [C++类的默认成员函数——lesson5.构造函数析构函数]
  • 第二十七章 ESP32S3 INFRARED_TRANSMISSION 实验
  • ✅ Python车牌识别计费系统 PyQt5界面 YOLOv5+CRNN 深度学习 MySQL可视化 车牌检测(建议收藏)