使用BeanUtils.copyProperties踩坑经历
1. 原始转换
提起对象转换,每个程序员都不陌生,比如项目中经常涉及到的DO、DTO、VO之间的转换,举个例子,假设现在有个OrderDTO,定义如下所示:
public class OrderDTO {
private long id;
private Long userId;
private String orderNo;
private Date gmtCreated;
// 省略get、set方法
}
有个OrderVO,定义如下所示:
public class OrderVO {
private long id;
private long userId;
private String orderNo;
private Date gmtCreated;
// 省略get、set方法
}
如果不使用任何转换工具,代码是下面这样的:
public static void main(String[] args) {
OrderDTO orderDTO = new OrderDTO();
orderDTO.setId(1L);
orderDTO.setUserId(123L);
orderDTO.setOrderNo("20210518000001");
orderDTO.setGmtCreated(new Date());
OrderVO orderVO = new OrderVO();
orderVO.setId(orderDTO.getId());
orderVO.setUserId(orderDTO.getUserId());
orderVO.setOrderNo(orderDTO.getOrderNo());
orderVO.setGmtCreated(orderDTO.getGmtCreated());
System.out.println(orderVO.getId());
System.out.println(orderVO.getUserId());
System.out.println(orderVO.getOrderNo());
System.out.println(orderVO.getGmtCreated());
}
运行结果:

2. 使用BeanUtils.copyProperties转换
因为项目中类似上面的转换多而繁琐,所以很多公司的项目中会使用Spring框架里的BeanUtils.copyProperties来做对象转换,代码如下所示:
OrderVO orderVO = new OrderVO();
BeanUtils.copyProperties(orderDTO, orderVO);
一行代码搞定,很方便,运行结果也和原来一模一样。
不过这个工具带来便利的同时,也带来了很多问题,稍微不注意就会踩坑,接下来就总结下使用这个工具常见的几个坑。
3. 踩坑经历
3.1 包装类型转基本类型问题
java.lang.IllegalArgumentException
细心的你可能会发现,OrderDTO中的userId字段,我定义的是Long类型:

而OrderVO中的userId字段,我定义的是long类型:

然后我们运行下下面所示的代码:
public static void main(String[] args) {
OrderDTO orderDTO = new OrderDTO();
orderDTO.setId(1L);
orderDTO.setUserId(null);
orderDTO.setOrderNo("20210518000001");
orderDTO.setGmtCreated(new Date());
OrderVO orderVO = new OrderVO();
BeanUtils.copyProperties(orderDTO, orderVO);
}
会看到代码抛了java.lang.IllegalArgumentException异常:

3.2 空格问题
假设OrderVO的orderNo字段,是用户自定义的,用户不小心输入了空格,使用BeanUtils.copyProperties后,空格会带入到OrderDTO的orderNo字段,如果不小心,就会把脏数据落到数据库(而我们希望的是去除空格再落库的),造成一系列后续问题:
public static void main(String[] args) {
OrderVO orderVO = new OrderVO();
orderVO.setId(1L);
orderVO.setUserId(123L);
// 模拟空格场景
orderVO.setOrderNo(" 20210518000001 ");
orderVO.setGmtCreated(new Date());
OrderDTO orderDTO = new OrderDTO();
BeanUtils.copyProperties(orderVO, orderDTO);
System.out.println(orderDTO.getOrderNo());
}
运行结果:

3.3 查找不到字段引用
使用BeanUtils.copyProperties后,会看到字段并没有引用,其实是有用到的,如下图所示:

有些小伙伴在看代码时,看到字段没有地方引用,可能就忍不住想删掉,结果就导致真正使用该字段的地方取不到值,产生bug。
3.4 前端误传字段,直接把数据库覆盖了
如果接口定义的比较严谨,理论上是不应该存在这种情况的,不过凡事总有特殊,这里举个接口不严谨导致数据被覆盖的例子。
假如OrderVO和OrderDTO有如下2个字段:
/**
* 已收金额
* 单位:分
*/
private Long receivedAmount;
/**
* 备注
*/
private String remark;
正常情况下,后端只应该使用前端传递的remark字段,receivedAmount字段不应该使用,但假如用户修改订单备注时,前端不小心传递了receivedAmount字段,并且赋值为null,这时使用BeanUtils.copyProperties后,OrderDTO里的receivedAmount字段就也为null,如果后端不知道前端传递了这个字段并且操作DB不够严谨,就会导致订单的已收金额被清空,很恐怖,而且不好排查原因。

4. 插件推荐
虽然BeanUtils.copyProperties工具提供了便利,但带来的问题也很多,因此很多公司(包含我现在所在的公司)都禁止在项目中使用该工具。
但重复的写对象转换,实在是太繁琐,效率太低了,这里推荐一个IDEA的插件GenerateAllSetter,可以一键生成对象的set方法,非常方便,如下图所示:

插件使用:
在需要生成set方法的对象上,按快捷键Option+Enter(Windows是Alt+Enter),会看到下图所示的选项:

点击后会自动生成所有字段(没有默认值)的赋值语句:

如果生成赋值语句时想带默认值,可以使用另一个选项:

效果如下所示:

使用BeanUtils.copyProperties踩坑经历的更多相关文章
- 『审慎』.Net4.6 Task 异步函数 比 同步函数 慢5倍 踩坑经历
异步Task简单介绍 本标题有点 哗众取宠,各位都别介意(不排除个人技术能力问题) —— 接下来:我将会用一个小Demo 把 本文思想阐述清楚. .Net 4.0 就有了 Task 函数 —— 异步编 ...
- TiDB 深度实践之旅--真实“踩坑”经历
美团点评 TiDB 深度实践之旅(9000 字长文 / 真实“踩坑”经历) 4 PingCAP · 154 天前 · 3956 次点击 这是一个创建于 154 天前的主题,其中的信息可能已经有所发 ...
- Net4.6 Task 异步函数 比 同步函数 慢5倍 踩坑经历
Net4.6 Task 异步函数 比 同步函数 慢5倍 踩坑经历 https://www.cnblogs.com/shuxiaolong/p/DotNet_Task_BUG.html 异步Task简单 ...
- myeclipse使用db-brower连接到sqlserver2012踩坑经历
myeclipse使用db-brower连接到sqlserver踩坑经历 首先得建立个角色 右键->创建登录名 权限开大点 连接设置 Driver template选择我选这个,格式按照我的写 ...
- sqlserver安装和踩坑经历
sqlserver安装和踩坑经历 下载 下载 安装 大致是按照这个来的 安装教程 出错 windows系统安装软件弹出"Windows installer service could not ...
- Dubbo 服务 IP 注册错误踩坑经历
个人博客地址 studyidea.cn,点击查看更多原创文章 踩坑 公司最近新建一个机房,需要将现有系统同步部署到新机房,部署完成之后,两地机房同时对提供服务.系统架构如下图: 这个系统当前对外采用 ...
- nginx搭建网站踩坑经历
为了更好的阅读体验,请访问我的个人博客 前言 早上刷抖音刷到一个只需要三步的nginx搭建教程(视频地址),觉得有些离谱,跟着复现了一遍,果然很多地方不严谨并且省略了大量步骤,对于很多不了解linux ...
- 【踩坑经历】一次Asp.NET小网站部署踩坑和解决经历
2013年给1个大学的小客户部署过一个小型的Asp.NET网站,非常小,用的sqlite数据库,今年人家说要换台服务器,要重新部署一下,好吧,虽然早就过了服务时间,但无奈谁叫人家是客户了,二话不说,上 ...
- RocketMQ同一个消费者唯一Topic多个tag踩坑经历
最近做的项目的一个版本需求中,需要用到MQ,对数据记录进行异步落库,这样可以减轻数据库的压力,同时可以抗住大量的数据落库.这里需要说明一下本人用到的MQ是公司自己在阿里的RokectMQ的基础上进行封 ...
随机推荐
- 基金 A 类和 C 类、ETF、LOF、QDII 到底是啥?
ETF 对于初入股市的新手来说,买了一只公司股票容易,想买一个行业的股票就不是很容易了. 比如你要懂得行业里都有谁,每个公司分配多少钱,最主要股票交易最少要交易 1 手也就是 100 股,要是想配置一 ...
- 颠覆你认知的Python3.9
我通读了python 3.9发行说明和相关的讨论.根据这些信息,我想写一个全面的指南,以便每个人都能一眼了解这些功能及其详细的工作原理 原文地址,点击这里,观看效果更佳 简而言之 从字典更新/合并到添 ...
- 制作API离线chm帮助文件教程
当我们开发好一个通信库的时候,我们希望给这个通信库配备一个帮助文档,最好的方式,就是有一个离线的chm版本的API文档,这样别人在使用的时候,就可以清楚看到命名空间.类的结构,同时也能看到每个方法和属 ...
- vps-java环境配置
昨天准备用vps打一台反序列化的机子要java环境,java环境安装了但是javac一直用不了,鼓捣了一段时间把配置文件给搞没了,只好重装vps,找了很多帖子才搞定,为了防止下次继续出现这种情况又要重 ...
- python之极简ATM系统示例
"""用户可登陆系统输错三次锁定账号用户可以创建新的用户名密码新用户初始账户设为0新用户可直接登陆系统用户登陆成功后可以选择业务类型用户数据可以根据业务修改输入Q随时退出 ...
- " "( 双引号) 与 ' '( 单引号) 差在哪?-- Shell十三问<第四问>
" "( 双引号) 与 ' '( 单引号) 差在哪?-- Shell十三问<第四问> 经过前面两章的学习,应该很清楚当你在 shell prompt 后面敲打键盘.直到 ...
- CPU发展史和相关品牌介绍
CPU发展史和相关品牌介绍 CPU发展已经有40多年的历史了.我们通常将其分成 六个阶段. (1)第一阶段 (1971年-1973年) .这是4位和8位低档微处理器时代,代表产品是Intel 4004 ...
- 结对作业-stage_1
教学班 罗杰.任建班周五3.4节 gitlab项目地址 Here it is. 成员 周远航(3004) 李辰洋(3477) 结对编程体验 感受 在前期设计时,两人合作可以收集更多资料,提供更多想法, ...
- leetcode 刷题(数组篇)152题 乘积最大子数组 (动态规划)
题目描述 给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积. 示例 1: 输入: [2,3,-2,4] 输出: 6 解释: 子 ...
- Python输入与输出
输出 print函数 语法: print(self, *args, sep=' ', end='\n', file=None) print函数是python中最常见的一个函数.用于将内容打印输出. p ...