Java对象字段拷贝最佳实践分享
作者:码农研究僧
文章介绍了几种常见的对象字段拷贝方法,包括手动set、BeanUtils.copyProperties、Lombok的@Builder和MapStruct,每种方法都有其优缺点和适用场景,推荐使用MapStruct,因为它在编译期生成代码,性能最优,支持复杂对象映射,需要的朋友可以参考下
前言
一开始在线表和历史表都是一张表,只不过字段设置不一样,显示不一样
但后续数据越来越多,为了不影响在线表,数据最终得落入历史表,不影响在线表的CRUD
以下章节围绕如何克隆在线表的数据
对象字段拷贝 的需求,比如从数据库查询出的对象需要转换成 DTO,或者在审核流程中更新一张表的同时写入历史表等
如果手动 set 字段,代码会变得繁琐,难以维护
1. 传统set(不推荐)
最简单的方法是 手动赋值,但是当字段较多时,代码冗长且容易遗漏。
示例代码:
CheckBoxDetailDO checkBoxDetailDO = new CheckBoxDetailDO(); checkBoxDetailDO.setCheckStatus(1L); checkBoxDetailDO.setCntr(checkBox.getCntr()); checkBoxDetailDO.setImgCntrF(checkBox.getImgCntrF()); checkBoxDetailDO.setCreateTime(checkBox.getCreateTime());
缺点:
- 代码冗长:如果 CheckBoxDO有几十个字段,手写 set 非常麻烦
- 易出错:如果 CheckBoxDO结构变化,必须手动修改所有 set 逻辑,维护成本高
适用场景:
字段较少(少于 3 个字段)
除了set,还有一种跟他很像,我也放在这个章节
Lombok 的 @Builder,可以使用 builder() 方法来 链式赋值,提高可读性(但我感觉没啥差异)
CheckBoxDetailDO checkBoxDetailDO = CheckBoxDetailDO.builder() .checkStatus(1L) .cntr(checkBox.getCntr()) .imgCntrF(checkBox.getImgCntrF()) .createTime(checkBox.getCreateTime()) .build();
2. copyProperties(有局限)
CheckBoxDetailDO checkBoxDetailDO = new CheckBoxDetailDO(); BeanUtils.copyProperties(checkBox, checkBoxDetailDO); checkBoxDetailDO.setCheckStatus(1L); // 额外赋值
优点:
- 代码简洁,自动拷贝 相同字段,避免手动 set
- 无需额外依赖,Spring 内置
缺点:
- 性能一般,使用了 反射,比手动 set 慢
- 字段名必须完全匹配,如果 CheckBoxDetailDO 和 CheckBoxDO字段名不一样,无法拷贝
- 不支持复杂转换,比如 数据类型不同(int vs String)、默认值设置 等
适用场景:
- 字段名和类型完全匹配的简单拷贝
- 项目已经使用 Spring,避免额外依赖
3. MapStruct(推荐)
如果 CheckBoxDO和 CheckBoxDetailDO 结构类似,并且字段较多,推荐使用 MapStruct 进行自动对象映射
MapStruct 在编译期生成代码,相比 BeanUtils 性能更优,并且支持字段转换
1、定义转换接口
创建一个 Mapper 接口,并用 @Mapper 注解标识。
import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.factory.Mappers; @Mapper(componentModel = "spring") public interface CheckBoxConverter { CheckBoxConverter INSTANCE = Mappers.getMapper(CheckBoxConverter.class); @Mapping(target = "checkStatus", constant = "1L") // 强制设定 checkStatus 为 1 CheckBoxDetailDO toDetailDO(CheckBoxDO checkBox); }
2、调用转换
@Resource private ChekBoxDetailMapper chekBoxDetailMapper; CheckBoxDetailDO checkBoxDetailDO = CheckBoxConverter.INSTANCE.toDetailDO(checkBox); chekBoxDetailMapper.insert(checkBoxDetailDO);
优点:
- 高性能,编译期生成代码,没有反射开销
- 自动映射字段,省去 set 代码
- 支持类型转换,例如 String -> Long、Date -> LocalDateTime 等
- 字段名不同也能映射,可以用 @Mapping(source = “oldField”, target = “newField”) 自定义映射关系
缺点:
需要引入 MapStruct 依赖,但一次配置,终身受益
适用场景:
- 字段较多且映射规则较复杂
- 项目对性能要求较高(比 BeanUtils 更快)
但是会有bug:
后续发现id自增字段也被复刻了!
采取忽略的方式:
@Mapper public interface CheckBoxConverter { CheckBoxConverter INSTANCE = Mappers.getMapper(CheckBoxConverter.class); @Mapping(target = "id", ignore = true) // 忽略 id 字段 @Mapping(target = "checkStatus", constant = "1L") // 强制设定 checkStatus 为 1 CheckBoxDetailDO toDetailDO(CheckBoxDO checkBox); }
截图如下:
这里拓展下这种方式其他的知识点:
使用 @BeanMapping(ignoreByDefault = true)(仅拷贝指定字段)
类似如下代码:
@Mapper(componentModel = "spring") public interface CheckBoxConverter { CheckBoxConverter INSTANCE = Mappers.getMapper(CheckBoxConverter.class); @BeanMapping(ignoreByDefault = true) @Mapping(target = "cntr", source = "cntr") @Mapping(target = "imgCntrF", source = "imgCntrF") @Mapping(target = "createTime", source = "createTime") @Mapping(target = "checkStatus", constant = "1L") CheckBoxDetailDO toDetailDO(CheckBox checkBox); }
如果不想修改代码:
CheckBoxDetailDO checkBoxDetailDO = CheckBoxConverter.INSTANCE.toDetailDO(checkBox); checkBoxDetailDO.setId(null); // 手动清除 id
最后,不要忘记insert,否则它只是一个对象,没有存储
CheckBoxDetailDO checkBoxDetailDO = CheckBoxConverter.INSTANCE.toDetailDO(checkBox); checkBoxDetailMapper.insert(checkBoxDetailDO); // 插入数据库
4. 总结
方案 | 代码简洁度 | 性能 | 适用场景 |
---|---|---|---|
手动 set | ❌ 差 | ✅ | 快 |
BeanUtils.copyProperties | ✅ 好 | ❌一般 | 字段完全匹配,简单拷贝 |
Lombok @Builder | ✅ 好 | ✅ 快 | 代码可读性强,构建新对象 |
MapStruct | ✅ 最优 | ✅ 最优 | 复杂对象映射,性能高 |
以上就是Java对象字段拷贝最佳实践分享的详细内容,更多关于Java对象字段拷贝的资料请关注脚本之家其它相关文章!