Java 17下优化实体部分字段更新代码(避免多if语句)
优化商品属性更新的几种方案
方案1:反射+自定义字段过滤
通过反射遍历DTO的非空字段,自动映射到已有Product实体,跳过DTO中不存在的实体字段(比如创建者信息),彻底避免硬编码if判断。
核心思路:
- 遍历DTO所有字段,以
null标记用户未设置的字段,这类字段直接跳过 - 反射获取Product中对应的字段并赋值更新
- 提前定义敏感字段列表,跳过无需更新的字段(如创建者ID、创建时间)
代码示例(Java):
import java.lang.reflect.Field; import java.util.Arrays; import java.util.HashSet; import java.util.Set; public class EntityUpdateUtil { // 定义Product中无需更新的敏感字段 private static final Set<String> IGNORED_FIELDS = new HashSet<>(Arrays.asList("creatorId", "createdTime")); public static void updateProductFromDto(Product product, ProductDto dto) throws IllegalAccessException { Class<?> dtoClass = dto.getClass(); Class<?> productClass = product.getClass(); for (Field dtoField : dtoClass.getDeclaredFields()) { dtoField.setAccessible(true); Object fieldValue = dtoField.get(dto); // 跳过用户未设置的字段 if (fieldValue == null) { continue; } String fieldName = dtoField.getName(); // 跳过敏感字段 if (IGNORED_FIELDS.contains(fieldName)) { continue; } try { Field productField = productClass.getDeclaredField(fieldName); productField.setAccessible(true); productField.set(product, fieldValue); } catch (NoSuchFieldException e) { // DTO字段在Product中不存在,直接跳过 continue; } } } }
注意事项:
- 若业务允许设置空值(比如把商品名称改为空字符串),需调整判断逻辑,比如用
Optional包装DTO字段,通过isPresent()判断是否需要更新 - 高频场景下建议缓存DTO和Product的字段映射关系,减少反射性能损耗
方案2:使用MapStruct实现条件映射
MapStruct是编译时生成映射代码的框架,支持条件映射、字段忽略、类型转换,性能接近手动编写的代码,且配置灵活。
步骤:
- 引入MapStruct依赖
- 定义Mapper接口,配置只映射非空字段,忽略敏感字段
代码示例(Java):
import org.mapstruct.Condition; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; @Mapper(componentModel = "spring") public interface ProductMapper { // 忽略Product中无需更新的敏感字段 @Mapping(target = "creatorId", ignore = true) @Mapping(target = "createdTime", ignore = true) void updateProductFromDto(ProductDto dto, @MappingTarget Product existingProduct); // 自定义条件:仅当字段非空时才映射 @Condition default boolean isNotNull(Object value) { return value != null; } // 针对数值类型的特殊判断(比如允许设置0库存) @Condition default boolean isQuantityValid(Integer quantity) { return quantity != null; // 若0是有效库存直接返回true,否则可改为quantity >= 0 } }
使用示例:
import org.springframework.stereotype.Service; @Service public class ProductService { private final ProductMapper productMapper; private final ProductRepository productRepository; public ProductService(ProductMapper productMapper, ProductRepository productRepository) { this.productMapper = productMapper; this.productRepository = productRepository; } public Product updateProduct(Long productId, ProductDto dto) { Product existingProduct = productRepository.findById(productId).orElseThrow(); productMapper.updateProductFromDto(dto, existingProduct); return productRepository.save(existingProduct); } }
优势:
- 编译时生成代码,无反射性能损耗
- 支持字段名映射(比如DTO的
productName对应Product的name,用@Mapping(source = "productName", target = "name")) - 自动处理类型转换(比如String转LocalDateTime)
方案3:建造者模式+反射增强
如果Product实现了建造者模式,可以基于原有实体创建建造者,通过反射调用建造者的set方法,避免硬编码if判断。
代码示例:
import java.lang.reflect.Field; import java.lang.reflect.Method; public Product updateProduct(Product existingProduct, ProductDto dto) throws IllegalAccessException, InvocationTargetException { Product.ProductBuilder builder = existingProduct.toBuilder(); Class<?> dtoClass = dto.getClass(); for (Field dtoField : dtoClass.getDeclaredFields()) { dtoField.setAccessible(true); Object fieldValue = dtoField.get(dto); if (fieldValue == null) { continue; } // 构建建造者方法名(比如字段name对应方法name()) String methodName = dtoField.getName(); try { Method builderMethod = builder.getClass().getMethod(methodName, dtoField.getType()); builderMethod.invoke(builder, fieldValue); } catch (NoSuchMethodException e) { continue; } } return builder.build(); }
注意:建造者的方法名需要和DTO字段名一致,或自定义映射规则适配差异。
关键注意事项
- 字段更新规则明确:区分“用户未设置字段”和“用户主动设置空值”,比如用
Optional包装DTO字段,避免误更新 - 敏感字段保护:无论哪种方案,都要明确忽略Product中不需要更新的字段,防止数据被篡改
- 类型兼容性:确保DTO字段类型与Product字段类型兼容,或在映射时添加类型转换逻辑
内容的提问来源于stack exchange,提问作者asdrubalo
相关产品推荐
相关产品推荐

