更新SubTeamMember未传createdBy触发非空约束异常的原因排查
createdBy触发SQL约束异常,但lastModifiedBy可正常为空 问题场景
你通过SubTeamMembersUpdateRequestDTO类更新数据时,createdBy和lastModifiedBy并非必填更新字段,因此未从Postman传入这两个值。系统可接受lastModifiedBy为空,但未传入createdBy时触发了SQL约束异常,报错提示Column 'created_by' cannot be null。
你的DTO类代码:
public class SubTeamMembersUpdateRequestDTO { private Long id; private Long subTeamId; private Long userId; private Integer target; private Date startDate; private Date endDate; private String memberType; private boolean isActive; private String createdBy; private String lastModifiedBy; }
控制台核心报错:
Caused by: java.sql.SQLIntegrityConstraintViolationException: Column 'created_by' cannot be null
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:117) ~[mysql-connector-java-8.0.17.jar:8.0.17]
核心原因分析
这个问题的本质是数据库约束、实体类配置/更新逻辑的双重差异导致的:
数据库字段约束不同
你的数据库表中,created_by字段被设置了NOT NULL约束,而last_modified_by字段允许为NULL(或有默认值)。当更新操作试图将created_by设为NULL时,数据库直接抛出约束异常;而last_modified_by因为允许空值,所以可以正常接受。更新逻辑对两个字段的处理不一致
对于lastModifiedBy,你的业务逻辑或框架(比如Spring Data JPA的审计功能)可能做了特殊处理:要么自动填充当前用户,要么在生成SQL更新语句时忽略该字段(如果DTO中为null),或者数据库有默认值兜底。
对于createdBy,更新逻辑没有做保护:当DTO中createdBy为null时,直接将这个null值映射到实体类,进而更新到数据库,触发了NOT NULL约束。而且createdBy作为创建人字段,更新操作本就不应该修改它,这是设计上的疏漏。
解决方案
根据你的场景,推荐以下几种修复方式:
1. 配置实体类,禁止更新createdBy字段
如果使用JPA/Hibernate,在实体类的createdBy字段上添加@Column(updatable = false)注解,这样框架在生成UPDATE语句时会自动忽略该字段,不会尝试更新它:
@Column(name = "created_by", updatable = false) private String createdBy;
如果用了Spring Data JPA的审计功能,@CreatedBy注解默认已经包含updatable = false的属性,确保创建人字段只在插入时赋值,更新时不会被修改。
2. 修改字段映射逻辑,只更新非空/需要的字段
在将DTO映射到实体类时,只复制DTO中非空的字段,或者明确指定需要更新的字段,避免覆盖createdBy的原有值:
- 使用
BeanUtils.copyProperties时,可以通过自定义方法忽略null值:
其中BeanUtils.copyProperties(dto, entity, getNullPropertyNames(dto));getNullPropertyNames方法用来获取DTO中为null的属性名,忽略这些属性的复制。 - 使用MapStruct等映射工具,通过
@Mapping(target = "createdBy", ignore = true)来忽略该字段的映射。
3. 检查并调整数据库约束(可选)
如果业务上确实允许created_by为null,可以修改数据库表结构,去掉该字段的NOT NULL约束。但通常created_by作为审计字段,应该在创建时就赋值且不允许为空,所以更推荐前两种方案。
内容的提问来源于stack exchange,提问作者Bhargav Netha

