使用 BeanUtils.copyProperties 踩坑经历

点击上方“Java基基”,选择“设为星标”

做积极的人,而不是积极废人!

每天 14:00 更新文章,每天掉亿点点头发...

源码精品专栏

 

来源:blog.csdn.net/zwwhnly/

article/details/117042348


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());
}

运行结果:

973737ba61c4d4dc210e20560672bbd0.png

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro

  • 视频教程:https://doc.iocoder.cn/video/

2. 使用BeanUtils.copyProperties转换

因为项目中类似上面的转换多而繁琐,所以很多公司的项目中会使用Spring框架里的BeanUtils.copyProperties来做对象转换,代码如下所示:

OrderVO orderVO = new OrderVO();
BeanUtils.copyProperties(orderDTO, orderVO);

一行代码搞定,很方便,运行结果也和原来一模一样。

不过这个工具带来便利的同时,也带来了很多问题,稍微不注意就会踩坑,接下来就总结下使用这个工具常见的几个坑。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud

  • 视频教程:https://doc.iocoder.cn/video/

3. 踩坑经历

3.1 包装类型转基本类型问题
java.lang.IllegalArgumentException

细心的你可能会发现,OrderDTO中的userId字段,我定义的是Long类型:

c9d72748a80021e3229fe04022bc2d14.png

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

b23f46c81789cab346b515b951260e79.png

然后我们运行下下面所示的代码:

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异常:

04cf56137bce85b8306fedceeac7a3eb.png
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());
}

运行结果:

b46f588720562656b3cff4b01d6ed163.png
3.3 查找不到字段引用

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

1e9457f367e717b39fcb7fe80cc3127d.png

有些小伙伴在看代码时,看到字段没有地方引用,可能就忍不住想删掉,结果就导致真正使用该字段的地方取不到值,产生bug。

3.4 前端误传字段,直接把数据库覆盖了

如果接口定义的比较严谨,理论上是不应该存在这种情况的,不过凡事总有特殊,这里举个接口不严谨导致数据被覆盖的例子。

假如OrderVO和OrderDTO有如下2个字段:

/**
 * 已收金额
 * 单位:分
 */
private Long receivedAmount;

/**
 * 备注
 */
private String remark;

正常情况下,后端只应该使用前端传递的remark字段,receivedAmount字段不应该使用,但假如用户修改订单备注时,前端不小心传递了receivedAmount字段,并且赋值为null,这时使用BeanUtils.copyProperties后,OrderDTO里的receivedAmount字段就也为null,如果后端不知道前端传递了这个字段并且操作DB不够严谨,就会导致订单的已收金额被清空,很恐怖,而且不好排查原因。

dc9595e352f036d10bb2ac283a3c52bb.png

4. 插件推荐

虽然BeanUtils.copyProperties工具提供了便利,但带来的问题也很多,因此很多公司(包含我现在所在的公司)都禁止在项目中使用该工具。

但重复的写对象转换,实在是太繁琐,效率太低了,这里推荐一个IDEA的插件GenerateAllSetter,可以一键生成对象的set方法,非常方便,如下图所示:

35c7a8393311c8fa0631de22af97120a.png

插件使用:

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

b8b37952e071f232efb7a8a0fc2aa2c7.png

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

c217f9b1b169b0533b22a6a211f69b76.png

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

4a3aed338f89f397681aafea17bc6b2e.png

效果如下所示:

bdb9327e08785a9ce66b617b444349a8.png

欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢

ca406262d0336374946b3635b235540a.png

已在知识星球更新源码解析如下:

1c8bad44f92969c4e455bb03a984cdb5.jpeg

f3a6eb4c801399b4f9ccd18e877153a3.jpeg

45cfe4238b2690c397119a47bb922e42.jpeg

f15e6e65964f71d9b9ab1957b3114677.jpeg

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 101 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 6W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用BeanUtils.copyProperties时需要注意以下几点: 1. 类型匹配:源对象和目标对象的属性类型必须匹配或可转换。如果类型不匹配,会抛出类型转换异常。 2. 属性名匹配:源对象和目标对象的属性名必须一致。如果属性名不一致,可以使用@PropertyName注解或者自定义转换器来处理。 3. 空值处理:默认情况下,如果源对象的属性值为null,目标对象对应的属性值也会被设置为null。如果不希望设置为null,可以使用BeanUtils.copyProperties(Object, Object, String[])方法,并指定需要忽略的属性。 4. 性能问题:BeanUtils.copyProperties使用反射实现,对于大量调用会有性能问题。如果需要处理大量对象的属性复制,可以考虑使用其他替代方案,如BeanMapper。 5. 异常处理:BeanUtils.copyProperties可能会抛出IllegalAccessException和InvocationTargetException异常。在使用时需要进行异常处理。 范例:<<引用:org.apache.commons.beanutils.BeanUtils.copyProperties(); 。 引用:一个BeanUtils.copyProperties的小型快速替代。 起因 由于BeanUtilsSpring或Apache Commons)的copyProperties实现是利用反射实现的,它在大量调用时具有比较严重的性能问题。 BeanMapper通过javassist类库实现在...。 请回答我或者给我介绍或演示一下:使用BeanUtils.copyProperties有什么注意点? 使用BeanUtils.copyProperties时需要注意以下几点: 1. 类型匹配 2. 属性名匹配 3. 空值处理 4. 性能问题 5. 异常处理
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值