You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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值:

  1. 定义映射接口:
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);
}
  1. 接口中注入使用:
@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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 04:40:18