
本文详解 spring boot 中更新 api 遇到数据库非空字段(如 dob)报错的根源,并提供「空值跳过更新」和「单请求体封装多模型」两种生产级解决方案,兼顾数据安全与接口简洁性。
本文详解 spring boot 中更新 api 遇到数据库非空字段(如 dob)报错的根源,并提供「空值跳过更新」和「单请求体封装多模型」两种生产级解决方案,兼顾数据安全与接口简洁性。
在构建 RESTful 更新接口时,常见误区是将 @RequestBody 直接映射为完整实体类,并无差别地调用所有 setter 方法——这会导致即使前端未提交某字段(如 dob),后端仍尝试将其 null 值写入数据库,触发 SQLIntegrityConstraintViolationException: Column 'dob' cannot be null 错误。根本原因在于:数据库字段设为 NOT NULL,但业务逻辑中该字段在部分更新场景下应保持原值,而非被 null 覆盖。
✅ 正确做法一:空值感知式更新(推荐)
不在 Service 层无条件赋值,而是显式判断字段是否为空,仅更新客户端实际传递的字段:
public String updateColumn(String patientId, OrgPatient orgPatient, PatientContact patientContact) {
Patient existPatient = patientRepository.findByPatientId(patientId);
if (existPatient == null) {
throw new RuntimeException("Patient not found: " + patientId);
}
OrgPatient updatedUser = orgPatientRepository.findByOrgPatientId(patientId);
PatientContact updatedContactUser = patientContactRepository.findByPatientId(patientId);
// ✅ 仅当请求体中提供了值时才更新 —— 关键防护
if (orgPatient.getFirstName() != null) updatedUser.setFirstName(orgPatient.getFirstName());
if (orgPatient.getMiddleName() != null) updatedUser.setMiddleName(orgPatient.getMiddleName());
if (orgPatient.getLastName() != null) updatedUser.setLastName(orgPatient.getLastName());
if (orgPatient.getDob() != null) updatedUser.setDob(orgPatient.getDob()); // ← 避免 null 写入
if (orgPatient.getGender() != null) updatedUser.setGender(orgPatient.getGender());
if (patientContact.getContactNumberType() != null)
updatedContactUser.setContactNumberType(patientContact.getContactNumberType());
if (patientContact.getContactNumer() != null)
updatedContactUser.setContactNumer(patientContact.getContactNumer());
if (patientContact.getContactAreaCode() != null)
updatedContactUser.setContactAreaCode(patientContact.getContactAreaCode());
orgPatientRepository.save(updatedUser);
patientContactRepository.save(updatedContactUser);
return "updated";
}
⚠️ 注意:此方式要求实体类字段类型使用包装类(如
LocalDate dob而非localDate),确保null可被准确识别;同时建议配合@JsonInclude(JsonInclude.Include.NON_NULL)在响应中隐藏空字段,保持 API 一致性。
✅ 正确做法二:统一请求体封装(解决双 @RequestBody 问题)
Spring MVC 不支持一个请求中绑定多个 @RequestBody(HTTP 请求体只能被读取一次),因此 @PutMapping 中声明两个 @RequestBody 参数会直接导致 HttpMessageNotReadableException。正确做法是定义一个聚合 DTO,将多模型结构扁平化或嵌套封装:
// 新增聚合请求体
public class PatientUpdateRequest {
private OrgPatient orgPatient;
private PatientContact patientContact;
// getters & setters
}
Controller 改为:
@PutMapping(value = "/update/{patientId}")
public ResponseEntity<string> updateOrg(
@PathVariable String patientId,
@RequestBody PatientUpdateRequest request) { // ← 单一、清晰的请求体
String ans = updateOrgPatientService.updateColumn(
patientId,
request.getOrgPatient(),
request.getPatientContact()
);
return ResponseEntity.ok(ans);
}</string>
对应 Postman 或前端请求 JSON 示例:
{
"orgPatient": {
"firstName": "John",
"lastName": "Doe"
// dob 字段完全省略 → 后端自动跳过更新
},
"patientContact": {
"contactNumberType": "MOBILE",
"contactNumer": "123-456-7890"
}
}
? 总结与最佳实践
-
永远不要盲目调用
setXxx(null):更新操作必须区分“显式置空”与“未提供”,前者需业务确认并加校验,后者应跳过。 -
DTO 优先于 Entity 直接接收:Controller 层应使用专用 DTO(如
PatientUpdateRequest),避免污染领域模型,也便于未来扩展(如添加版本号、审计字段)。 -
补充防御性校验:可在 Controller 层用
@Valid配合自定义约束(如@NotNullIfPresent),或在 Service 层对必填更新字段做前置检查,返回400 Bad Request并附带明确错误信息。 -
考虑 PATCH 语义:若更新粒度更细,建议改用
@PatchMapping并采用 JSON Patch(RFC 6902)标准,天然支持部分更新语义。
通过以上改造,你既能彻底规避 Column cannot be null 异常,又能构建出符合 REST 规范、易于维护、面向演进的更新接口。










