Spring Boot中Patch请求仅更新传入数据,其余字段置空问题求助
Spring Boot Patch接口部分字段更新避免置空的解决方案
你当前的代码会把AccountDTO中未传入的字段(值为null)直接覆盖到数据库实体的对应字段,导致原有数据被置空。因为@RequestBody绑定请求体时,未传的字段会被初始化为null,而你直接调用setXxx方法赋值,不管值是否为null。
以下是几种实用的解决方案:
方案1:手动判断非空后赋值
最直接的方式,仅更新DTO中不为null的字段:
@PatchMapping(UPDATE_ACCOUNT) public ResponseEntity<AccountEntity> partialUpdate(@PathVariable("accountId") Long accountId, @RequestBody AccountDTO accountDTO) { AccountEntity entitySaved = accountRepository.findById(accountId) .orElseThrow(() -> new NotFoundException("Нет такого ID")); // 仅当DTO字段不为null时,才更新实体字段 if (accountDTO.getIik() != null) { entitySaved.setIik(accountDTO.getIik()); } if (accountDTO.getBik() != null) { entitySaved.setBik(accountDTO.getBik()); } if (accountDTO.getBank_name() != null) { entitySaved.setBank_name(accountDTO.getBank_name()); } if (accountDTO.getCurrency() != null) { entitySaved.setCurrency(accountDTO.getCurrency()); } final AccountEntity updateAccount = accountRepository.save(entitySaved); return ResponseEntity.ok(updateAccount); }
优点:简单直观,无额外依赖;缺点:字段较多时代码冗余。
方案2:使用Spring BeanUtils忽略null字段复制
通过工具方法过滤null字段,再调用BeanUtils.copyProperties复制:
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanWrapperImpl; import java.util.HashSet; import java.util.Set; // 工具方法:获取对象中所有值为null的属性名 private static String[] getNullPropertyNames(Object source) { final BeanWrapper src = new BeanWrapperImpl(source); java.beans.PropertyDescriptor[] pds = src.getPropertyDescriptors(); Set<String> emptyNames = new HashSet<>(); for (java.beans.PropertyDescriptor pd : pds) { Object srcValue = src.getPropertyValue(pd.getName()); if (srcValue == null) { emptyNames.add(pd.getName()); } } return emptyNames.toArray(new String[0]); } // 接口实现 @PatchMapping(UPDATE_ACCOUNT) public ResponseEntity<AccountEntity> partialUpdate(@PathVariable("accountId") Long accountId, @RequestBody AccountDTO accountDTO) { AccountEntity entitySaved = accountRepository.findById(accountId) .orElseThrow(() -> new NotFoundException("Нет такого ID")); // 仅复制DTO中非null的字段到实体 BeanUtils.copyProperties(accountDTO, entitySaved, getNullPropertyNames(accountDTO)); final AccountEntity updateAccount = accountRepository.save(entitySaved); return ResponseEntity.ok(updateAccount); }
优点:字段多时代码简洁;缺点:需编写工具类,对嵌套对象处理有限。
方案3:用Map接收请求体,反射更新实体
直接接收请求体为Map<String, Object>,通过反射匹配实体字段更新:
import java.lang.reflect.Field; @PatchMapping(UPDATE_ACCOUNT) public ResponseEntity<AccountEntity> partialUpdate(@PathVariable("accountId") Long accountId, @RequestBody Map<String, Object> updates) { AccountEntity entitySaved = accountRepository.findById(accountId) .orElseThrow(() -> new NotFoundException("Нет такого ID")); // 遍历更新字段,反射修改实体 updates.forEach((key, value) -> { try { Field field = AccountEntity.class.getDeclaredField(key); field.setAccessible(true); field.set(entitySaved, value); } catch (NoSuchFieldException | IllegalAccessException e) { // 可添加日志记录异常,忽略不存在的字段 e.printStackTrace(); } }); final AccountEntity updateAccount = accountRepository.save(entitySaved); return ResponseEntity.ok(updateAccount); }
优点:无需DTO类,灵活处理任意字段;缺点:需处理反射异常,类型转换可能出错,需额外校验字段合法性。
方案4:结合Spring Data JPA的@DynamicUpdate
在AccountEntity类上添加@DynamicUpdate注解,JPA会自动生成仅包含修改字段的SQL语句:
import javax.persistence.Entity; import javax.persistence.DynamicUpdate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; import javax.persistence.EntityListeners; @Entity @DynamicUpdate // 开启动态更新SQL @EntityListeners(AuditingEntityListener.class) public class AccountEntity { // 实体字段、getter/setter等 }
此方案需配合方案1或方案2使用,确保仅修改需要更新的字段,避免null值覆盖原有数据。
方案5:使用MapStruct映射器(推荐)
若项目已引入MapStruct,可定义映射器时忽略null值:
- 定义映射接口:
import org.mapstruct.Mapper; import org.mapstruct.MappingTarget; import org.mapstruct.NullValuePropertyMappingStrategy; @Mapper(componentModel = "spring", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) public interface AccountMapper { void updateAccountFromDto(AccountDTO accountDTO, @MappingTarget AccountEntity accountEntity); }
- 接口中注入使用:
@Autowired private AccountMapper accountMapper; @PatchMapping(UPDATE_ACCOUNT) public ResponseEntity<AccountEntity> partialUpdate(@PathVariable("accountId") Long accountId, @RequestBody AccountDTO accountDTO) { AccountEntity entitySaved = accountRepository.findById(accountId) .orElseThrow(() -> new NotFoundException("Нет такого ID")); accountMapper.updateAccountFromDto(accountDTO, entitySaved); final AccountEntity updateAccount = accountRepository.save(entitySaved); return ResponseEntity.ok(updateAccount); }
优点:类型安全,代码简洁,支持复杂对象映射;缺点:需引入MapStruct依赖。
内容的提问来源于stack exchange,提问作者Borka192
相关产品推荐
相关产品推荐

