mongodb 必填字段校验须通过集合级 json schema 实现,设 validationlevel: "strict" 和 validationaction: "error";默认值需由应用层赋值或 $setoninsert 设置,不可依赖驱动或聚合临时兜底。

插入时强制校验必填字段用 schema validation
MongoDB 本身不支持传统 SQL 的 NOT NULL 约束,但可以在集合层面启用 validationLevel 和 validationAction 配合 JSON Schema 实现插入/更新时的字段存在性检查。关键不是靠驱动层“约定”,而是靠服务端校验落地。
常见错误是只在应用代码里做 if (!user.name) 判断,漏掉直连数据库的脚本、其他服务或误操作。必须把校验逻辑下沉到 MongoDB 层。
- 创建集合时指定
validator:要求name字段存在且为字符串,email必须匹配邮箱格式 -
validationLevel: "strict"表示对所有写操作生效(包括update) -
validationAction: "error"让非法文档直接失败,而不是静默记录到system.validation.errors
db.createCollection("users", {
validator: {
$jsonSchema: {
required: ["name", "email"],
properties: {
name: { bsonType: "string", minLength: 1 },
email: { bsonType: "string", pattern: ".+@.+" }
}
}
},
validationLevel: "strict",
validationAction: "error"
})
默认值不能靠 MongoDB 自动填充,得在应用层或聚合管道里补
MongoDB 没有 DEFAULT 关键字,也没有类似 MySQL 的 DEFAULT CURRENT_TIMESTAMP 机制。所谓“默认值”只能由三类地方提供:应用代码写入前赋值、驱动层中间件(如 Mongoose 的 default)、或读取时用 $ifNull 在聚合中兜底。
容易踩的坑是以为加了 Mongoose 的 default: Date.now 就万无一失——一旦绕过 ORM(比如用 mongosh 直接 insertOne),字段就真的为空。
- 应用写入前统一处理:
user.createdAt = user.createdAt || new Date() - Mongoose 中定义
createdAt: { type: Date, default: Date.now },仅对通过该模型实例的save()生效 - 查询时补默认值:
{$addFields: {status: {$ifNull: ["$status", "active"]}}}
更新操作中避免覆盖已存在的字段值
用 updateOne 或 findOneAndUpdate 时,如果传入一个不带 status 的更新对象,又没加 $setOnInsert,就可能把原来有值的 status 变成 null 或丢失。
真正安全的做法是区分“首次插入”和“后续更新”,把默认值逻辑绑定到插入动作上。
- 用
upsert: true+$setOnInsert:只在文档不存在时设置默认字段 - 不要在
$set里硬写默认值,比如{$set: {status: "active"}}会强制覆盖 - 需要动态默认值(如当前时间)时,用
$setOnInsert: {createdAt: {$dateNow: {}}}(4.2+ 支持)
db.users.updateOne(
{ _id: ObjectId("...") },
{
$setOnInsert: { createdAt: { $dateNow: {} } },
$set: { updatedAt: { $dateNow: {} } }
},
{ upsert: true }
)
嵌套字段和数组元素的必填/默认值更难控制
JSON Schema 能校验 address.city 是否存在,但无法阻止用户传入 {address: {}} 这种空对象——因为 address 字段本身存在,只是其子字段缺失。这时候要靠更细粒度的 required 和 properties 嵌套定义。
默认值同理:Mongoose 的 address: { city: { type: String, default: "Unknown" } } 只在 address 对象被创建时生效;如果 address 已存在但 city 为 null,它不会自动重置。
- JSON Schema 中对嵌套字段设
required: ["address"], address: { required: ["city"] } - 聚合中补嵌套默认值需多层
$ifNull:$ifNull: ["$address.city", "Unknown"] - 更新嵌套字段默认值建议用
$setOnInsert: {"address.city": "Unknown"},而非整个address对象
$setOnInsert 是唯二能真正把住底线的手段;其余都依赖开发自觉或框架约束,上线后极易失效。











