如何更简洁地将表单对象数据传输至实体以重构更新接口代码?
嘿,我来给你几个实用的重构方案,能帮你把那些繁琐的setter代码彻底干掉,让更新逻辑清爽很多!
方案1:用Spring自带的
BeanUtils(零额外依赖) 这是最省事的办法,Spring框架本身就提供了BeanUtils工具类,能直接把表单对象的属性拷贝到实体里,前提是两者的字段名称和类型要匹配。
重构后的代码大概是这样:
@PutMapping("products/{productId}") public ProductResponse updateProduct(@PathVariable("productId") Long productId, @RequestBody ProductForm productForm) { Product foundProduct = productRepository.findById(productId) .orElseThrow(() -> new EntityNotFoundException("productId " + productId + " not found.")); // 核心就这一行! BeanUtils.copyProperties(productForm, foundProduct); // 保存并返回响应 Product updatedProduct = productRepository.save(foundProduct); return convertToResponse(updatedProduct); }
注意:如果有不需要拷贝的字段(比如实体里的id、createTime),可以用第三个参数指定忽略:
BeanUtils.copyProperties(productForm, foundProduct, "id", "createTime");
另外,如果表单里的null值不想覆盖实体的原有值,你可以自己封装一个工具方法,过滤掉null属性再拷贝。
方案2:用
ModelMapper(灵活的对象映射工具) 如果你的表单和实体字段名不完全一致,或者需要自定义映射规则,ModelMapper会更灵活。
首先要加依赖(Maven为例):
<dependency> <groupId>org.modelmapper</groupId> <artifactId>modelmapper</artifactId> <version>3.1.1</version> </dependency>
然后在Spring容器里注册ModelMapper bean:
@Bean public ModelMapper modelMapper() { return new ModelMapper(); }
接下来就能在控制器里注入使用了:
@Autowired private ModelMapper modelMapper; @PutMapping("products/{productId}") public ProductResponse updateProduct(@PathVariable("productId") Long productId, @RequestBody ProductForm productForm) { Product foundProduct = productRepository.findById(productId) .orElseThrow(() -> new EntityNotFoundException("productId " + productId + " not found.")); // 拷贝属性,支持自定义映射规则 modelMapper.map(productForm, foundProduct); Product updatedProduct = productRepository.save(foundProduct); return convertToResponse(updatedProduct); }
要是字段名不一样,比如表单里的productTitle对应实体的title,可以配置映射规则:
modelMapper.typeMap(ProductForm.class, Product.class) .addMapping(ProductForm::getProductTitle, Product::setTitle);
方案3:用
MapStruct(编译时生成代码,性能最优) MapStruct是编译时生成映射代码的工具,比反射实现的BeanUtils、ModelMapper性能更好,而且能在编译期发现映射错误,适合大型项目。
先加依赖(Maven为例):
<dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>1.5.5.Final</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.5.5.Final</version> <scope>provided</scope> </dependency>
然后写一个Mapper接口,用@MappingTarget标注要更新的实体:
@Mapper(componentModel = "spring") public interface ProductMapper { // 把表单属性更新到实体里 void updateProductFromForm(ProductForm form, @MappingTarget Product product); }
最后在控制器里注入这个Mapper,调用方法就行:
@Autowired private ProductMapper productMapper; @PutMapping("products/{productId}") public ProductResponse updateProduct(@PathVariable("productId") Long productId, @RequestBody ProductForm productForm) { Product foundProduct = productRepository.findById(productId) .orElseThrow(() -> new EntityNotFoundException("productId " + productId + " not found.")); // 调用自动生成的映射方法 productMapper.updateProductFromForm(productForm, foundProduct); Product updatedProduct = productRepository.save(foundProduct); return convertToResponse(updatedProduct); }
如果有字段映射差异,直接在接口上配置@Mapping:
@Mapper(componentModel = "spring") public interface ProductMapper { @Mapping(source = "productTitle", target = "title") void updateProductFromForm(ProductForm form, @MappingTarget Product product); }
小提醒
- 如果是部分更新(比如前端只传了几个字段),要注意不要把实体里的原有非空值覆盖成null,这时候可以给MapStruct或者ModelMapper配置忽略null值的规则,或者自己写工具方法处理。
- 优先选BeanUtils(零依赖)或者MapStruct(性能好),ModelMapper适合需要灵活映射的场景。
内容的提问来源于stack exchange,提问作者Brett Freeman
相关产品推荐
相关产品推荐

