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

Spring Boot JPA操作MySQL更新实体时非空字段报错求助

解决Spring Boot JPA更新时忽略非空字段的问题

嘿,这个问题我太熟了!我之前在做Spring Boot项目的时候也踩过一模一样的坑——明明数据库里那个非空字段已经有值了,更新的时候没传它居然还报NOT NULL错误,简直离谱。结合你的实体类(已经加了@DynamicUpdate(true)和@JsonInclude(NON_EMPTY)),给你几个实用的解决方案,按需选就行:

1. 永久禁止该字段被更新(适合一旦创建就不能改的字段)

如果这个非空字段是创建后就永远不需要修改的(比如用户的注册时间、初始创建的标识字段),直接在字段上加上updatable = false属性,JPA会在生成UPDATE语句时完全忽略这个字段,根本不会碰它:

@Entity
@Table(name = "user")
@JsonInclude(value = JsonInclude.Include.NON_EMPTY)
@DynamicUpdate(true)
public class User {
    private static final long serialVersionUID = 1L;

    // 假设这个是你的非空字段,比如用户的手机号,创建后不能改
    @Column(name = "phone", nullable = false, updatable = false)
    private String phone;

    // 其他字段...
    // getter和setter...
}

2. 动态忽略null值(适合字段偶尔需要更新的场景)

如果这个字段有时候还是需要修改,只是这次更新不想传它,那最好用DTO(数据传输对象)配合BeanUtils来实现“只更新非null的字段”:

第一步:创建更新专用的DTO

只在DTO里放允许更新的字段,排除那个不想被覆盖的非空字段:

public class UserUpdateDTO {
    // 比如只允许更新用户名和邮箱,排除phone字段
    private String username;
    private String email;

    // getter和setter...
}

第二步:添加工具方法过滤null属性

写一个工具方法,找出DTO中为null的属性名,这样BeanUtils复制的时候会跳过这些属性:

import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import java.beans.PropertyDescriptor;
import java.util.HashSet;
import java.util.Set;

public class BeanUtil {
    public static String[] getNullPropertyNames(Object source) {
        final BeanWrapper src = new BeanWrapperImpl(source);
        PropertyDescriptor[] pds = src.getPropertyDescriptors();
        Set<String> emptyNames = new HashSet<>();
        for (PropertyDescriptor pd : pds) {
            Object srcValue = src.getPropertyValue(pd.getName());
            if (srcValue == null) {
                emptyNames.add(pd.getName());
            }
        }
        return emptyNames.toArray(new String[0]);
    }
}

第三步:在服务层实现更新逻辑

先从数据库查出原实体,再用DTO的非null属性覆盖原实体,最后保存:

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    public User updateUser(Long userId, UserUpdateDTO updateDTO) {
        // 先获取数据库中已有的用户实体
        User existingUser = userRepository.findById(userId)
                .orElseThrow(() -> new RuntimeException("用户不存在"));
        
        // 只复制DTO中非null的属性,跳过null值,原实体的非空字段不会被覆盖
        BeanUtils.copyProperties(updateDTO, existingUser, BeanUtil.getNullPropertyNames(updateDTO));
        
        return userRepository.save(existingUser);
    }
}

3. 自定义更新SQL(最灵活的方式)

如果不想用DTO,也可以直接在Repository里写自定义的UPDATE语句,只更新你需要的字段,完全不涉及那个非空字段:

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    @Modifying
    @Query("UPDATE User u SET u.username = :username, u.email = :email WHERE u.id = :userId")
    int updateUserFields(@Param("userId") Long userId, 
                        @Param("username") String username, 
                        @Param("email") String email);
}

调用这个方法时,直接传需要更新的字段就行,那个非空字段根本不会出现在SQL里,自然不会触发NOT NULL错误。

补充:为什么你的现有注解没起作用?

  • @DynamicUpdate(true):只会让JPA生成UPDATE语句时只包含实体中发生变化的字段,但如果前端没传某个字段,JSON反序列化后实体的该字段会被设为null,JPA会认为这个字段“变了”,还是会把null写到UPDATE语句里,导致数据库报错。
  • @JsonInclude(NON_EMPTY):这是序列化时用的(把实体转成JSON时忽略空值),但反序列化(把前端传的JSON转成实体)时,没传的字段还是会被设为null,所以解决不了更新的问题。

内容的提问来源于stack exchange,提问作者Veera

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 08:19:30