Mongoose中文文档-指南之模式(Schemas)


Schema即模式,在Mongoose中模式是和MongoDB集合的映射,我们使用Mongoose都是从定义模式开始的。

  1. 定义Schema
  2. 创建模型(Model)
  3. 实例方法
  4. 静态方法
  5. 查询助手
  6. 索引
  7. 虚拟属性
  8. 别名
  9. 选项参数(Options)
  10. 可插件化(Pluggable)
  11. 延伸阅读

在使用本指南之前,首先你应该对Mongoose有所了解,否则建议先阅读快速入门,以了解Mongoose的工作原理。如果是从4.x迁移到5.x,请参考迁移指南

1. 定义Schema

Schema是Mongoose的起点,在本文档中我们将Schema翻译为模式。每个模式都是对MongoDB集合(collection)的映射,其表示对所映射集合中文档(document)结构的定义。

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var blogSchema = new Schema({
  title:  String,
  author: String,
  body:   String,
  comments: [{ body: String, date: Date }],
  date: { type: Date, default: Date.now },
  hidden: Boolean,
  meta: {
    votes: Number,
    favs:  Number
  }
});

定义完模式后,其后仍可以通过Schema#add方法来添加键/路径(path)。

上以代码blogSchema中的每个键都定义了文档中一个属性,该属性将被强制转换为与其关联的SchemaType。例如,在上面我们定义了一个title属性,它将被转换为String类型的SchemaType;而date属性将被转换为Date类型的SchemaType。还可以为键指定包含其它键/类型定义的嵌套对象,如上面的meta属性。

所支持的SchemaType有:

更多关于SchemaType的介绍

模式不仅定义了文档的结构和属性的转换,还定义了文档实例方法静态模型方法复合索引和称之为中间件的文档生命周期钩子。


2. 创建模型(Model)

模式(Schema)并不能操作集合和文档,我们还需要将blogSchema转换为可以工作的模型(Model)。可以通过mongoose.model(modelName, schema)方法来转换:

var Blog = mongoose.model('Blog', blogSchema);
// ready to go!


3. 实例方法

Model是一个与MongoDB交互的主要工具,Model类的实例是一个DocumentModel本身包含一些实例方法。此外,我们还可添加自定义的实例方法:

// define a schema
var animalSchema = new Schema({ name: String, type: String });

// assign a function to the "methods" object of our animalSchema
animalSchema.methods.findSimilarTypes = function(cb) {
  return this.model('Animal').find({ type: this.type }, cb);
};

如上所示,添加到模式的methods属性上的方法,都会最终成为Model的实例方法。

以上我们为所有的animal实例添加了一个findSimilarTypes方法。可以像下同这样使用这个方法:

var Animal = mongoose.model('Animal', animalSchema);
var dog = new Animal({ type: 'dog' });

dog.findSimilarTypes(function(err, dogs) {
  console.log(dogs); // woof
});

请注意:

  • 不要覆盖默认的mongoose文档方法,否则可能会导致不可预测的结果。请参阅:更多介绍
  • 在上面示例中,我们使用Schema.methods属性对象直接保存了实例方法,除此之外,还可以通过Schema#method()辅助方法来添加
  • 不要使用ES6的箭头函数(=>来声明方法,箭头函数会阻止绑定this,并导致所添加的方法不能访问文档(Model的实例是一个文档对象),以上示例将不能正常运行


4. 静态方法

Model添加静态方法同样很简单。如,可以像下面这样为animalSchema添加一个静态方法:

// assign a function to the "statics" object of our animalSchema
animalSchema.statics.findByName = function(name, cb) {
  return this.find({ name: new RegExp(name, 'i') }, cb);
};

var Animal = mongoose.model('Animal', animalSchema);
Animal.findByName('fido', function(err, animals) {
  console.log(animals);
});

如上所示,添加到模式的statics属性上的方法,都会最终成为Model的静态方法,这些方法和Model本身的静态方法(如:Model.find()Model.findOne()等)一样调用。

注意:添加静态方法时,不要使用ES6的箭头函数(=>来声明方法,箭头函数会阻止绑定this,以上示例将不能正常运行。


5. 查询助手

你同样可以添加查询辅助(查询助手)函数,它们类似于实例方法,但用于Mongoose的查询中。这使你可以扩展Mongoose的链式查询构建器API

animalSchema.query.byName = function(name) {
  return this.where({ name: new RegExp(name, 'i') });
};

var Animal = mongoose.model('Animal', animalSchema);

Animal.find().byName('fido').exec(function(err, animals) {
  console.log(animals);
});

Animal.findOne().byName('fido').exec(function(err, animal) {
  console.log(animal);
});


6. 索引

MongoDB支持二级索引。通过Mongoose,我们可以在Schema路径级别schema级别中定义这些索引。但如果要创建复合索引时,则必须在schema级别中定义。

var animalSchema = new Schema({
  name: String,
  type: String,
  tags: { type: [String], index: true } // field level
});

animalSchema.index({ name: 1, type: -1 }); // schema level

当应用启时,Mongoose会自动为Schema中每个己定义的索引调用createIndex。Mongoose会按顺序为每个索引调用createIndex,并会在所有createIndex调用成功或出错误时在Model上发出'index'事件。虽然这很方法,但建议在生产环境中禁用此行为,因为创建索引会对性能产生重大影响。可以将autoIndex选项设置为false来禁用该行为,或者设置全局连接的autoIndex属性为false来禁用。

mongoose.connect('mongodb://user:pass@localhost:port/database', { autoIndex: false });
// or
mongoose.createConnection('mongodb://user:pass@localhost:port/database', { autoIndex: false });
// or
animalSchema.set('autoIndex', false);
// or
new Schema({..}, { autoIndex: false });

当索引构建完成或发生错误时,Mongoose将在模型上发出'index'事件。

// Will cause an error because mongodb has an _id index by default that
// is not sparse
animalSchema.index({ _id: 1 }, { sparse: true });
var Animal = mongoose.model('Animal', animalSchema);

Animal.on('index', function(error) {
  // "_id index cannot be sparse"
  console.log(error.message);
});

更多介绍参阅:Model#ensureIndexes


7. 虚拟属性

虚拟属性(Virtuals)可以获取和设置的文档属性,但不会持久保存到MongoDB。如,虚拟getter对于格式化或组合字段很有用,而虚拟setter可用于将单个值解组成多个值以进行存储。

// define a schema
var personSchema = new Schema({
  name: {
    first: String,
    last: String
  }
});

// compile our model
var Person = mongoose.model('Person', personSchema);

// create a document
var axl = new Person({
  name: { first: 'Axl', last: 'Rose' }
});

以上我们定义了下包含firstlast嵌套对象的name属性。如果要打印全名,就需要像下面这样:

console.log(axl.name.first + ' ' + axl.name.last); // Axl Rose

这时,我们可以定义一个虚拟的getterfullName,该属性不会持久化到MongoDB中。

personSchema.virtual('fullName').get(function () {
  return this.name.first + ' ' + this.name.last;
});

现在,可以通过fullName来获取用户的全名:

console.log(axl.fullName); // Axl Rose

如果你使用toJSON()toObject(),默认情况下mongoose将不包含虚拟属性。这包括在Mongoose文档上调用JSON.stringify()的输出,因为JSON.stringify()调用了JSON()。这时,可以将{virtuals: true}传递给toObject()toJSON()

您还可以向虚拟属性添加自定义setter,以便通过fullName虚拟属性来设置firstlast

personSchema.virtual('fullName').
  get(function() { return this.name.first + ' ' + this.name.last; }).
  set(function(v) {
    this.name.first = v.substr(0, v.indexOf(' '));
    this.name.last = v.substr(v.indexOf(' ') + 1);
  });

axl.fullName = 'William Rose'; // Now `axl.name.first` is "William"

虚拟属性设置器(setters)会在其他验证之前应用。因此,即使firstlast字段为必须时,上面的例子仍然有效。

只有非虚拟属性才能作为查询和字段选择的一部分。因为虚拟属性没有存储在MongoDB中,所以无法使用它们进行查询。


8. 别名

别名(Aliases)是一种特殊类型的虚拟属性,其中getter和setter无缝地获取和设置另一个属性。这对于节省网络带宽很方便,你可以将存储在数据库中的短属性名称转换为更长的名称以便于代码可读性。

var personSchema = new Schema({
  n: {
    type: String,
    // Now accessing `name` will get you the value of `n`, and setting `n` will set the value of `name`
    alias: 'name'
  }
});

// Setting `name` will propagate to `n`
var person = new Person({ name: 'Val' });
console.log(person); // { n: 'Val' }
console.log(person.toObject({ virtuals: true })); // { n: 'Val', name: 'Val' }
console.log(person.name); // "Val"

person.name = 'Not Val';
console.log(person); // { n: 'Not Val' }

还可以在嵌套路径上声明别名。使用嵌套schema和子文档更容易,可以只要使用完整的嵌套路径nested.myProp作为别名,也可以内联声明嵌套路径别名。

const childSchema = new Schema({
  n: {
    type: String,
    alias: 'name'
  }
}, { _id: false });

const parentSchema = new Schema({
  // If in a child schema, alias doesn't need to include the full nested path
  c: childSchema,
  name: {
    f: {
      type: String,
      // Alias needs to include the full nested path if declared inline
      alias: 'name.first'
    }
  }
});


9. 选项参数(Options)

模式有一些可配置的选项,可以传递给构造函数或直接set

new Schema({..}, options);

// or

var schema = new Schema({..});
schema.set(option, value);

以下是所支持选项参数:


option: autoIndex

默认情况下,Mongoose的init()函数会在成功连接到MongoDB之后调用Model.createIndexes()来创建模型的模式中定义的所有索引。自动创建索引非常适合开发和测试环境,但是索引构建也会对生产数据库造成很大的负担。如果要在生产中精细化管理索引,可以将autoIndex设置为false

const schema = new Schema({..}, { autoIndex: false });
const Clock = mongoose.model('Clock', schema);
Clock.ensureIndexes(callback);

autoIndex的默认值为true,可以通过mongoose.use('autoIndex', false);来修改这一默认设置。


autoCreate

在Mongoose构建索引之前,如果autoCreate设置为true,它会调用Model.createCollection()在MongoDB中创建基础集合。如果设置了schema的capped选项,则createCollection()会根据collation选项设置集合的默认排序规则,并将集合建立为上限集合。与autoIndex一样,将autoCreate设置为true对开发和测试环境很有帮助。

createCollection()无法更改现有集合。如,如果向模式添加capped:1024并且现有集合没有上限,则createCollection()将引发错误。一般来说,在生产环境中应该将autoCreate设置为false

const schema = new Schema({..}, { autoCreate: true, capped: 1024 });
const Clock = mongoose.model('Clock', schema);
// Mongoose will create the capped collection for you.

autoCreate的默认设置值为true(不同于autoIndex),可以通过mongoose.use('autoCreate', true);来修改这一默认设置。


option: bufferCommands

默认情况下,当连接断开时,Mongoose会缓冲命令,直到驱动程序重新连接。如果要禁用缓冲,可以将bufferCommands设置为false

var schema = new Schema({..}, { bufferCommands: false });

Schema的bufferCommands设置会重写全局的bufferCommands选项。

mongoose.set('bufferCommands', true);
// Schema option below overrides the above, if the schema option is set.
var schema = new Schema({..}, { bufferCommands: false });


option: capped

Mongoose支持MongoDB的capped选项。要指定基础MongoDB集合的上限,需要将capped选项设置为集合的最大大小(以字节为单位)。

new Schema({..}, { capped: 1024 });

如果要传递maxautoIndexId等附加选项,也可以将capped选项设置为对象。在这种情况下,必须显式传递size选项。

new Schema({..}, { capped: { size: 1024, max: 1000, autoIndexId: true } });


option: collection

默认情况下,Mongoose通过将模型名称传递给utils.toCollectionName方法来生成集合名称。该方法会使名称多元化,如果你的集合需要不同的名称,则需要设置此选项。

var dataSchema = new Schema({..}, { collection: 'data' });


option: id

Mongoose默认为每个模式分配一个名为id的虚拟getter,它会返回文件的_id字段并将其强制转换为字符串,或者对ObjectId类型的字段返回其hexString。如果你不希望将idgetter添加到模式中,则可以在Schema构建时禁用它。

// default behavior
var schema = new Schema({ name: String });
var Page = mongoose.model('Page', schema);
var p = new Page({ name: 'mongodb.org' });
console.log(p.id); // '50341373e894ad16347efe01'

// disabled id
var schema = new Schema({ name: String }, { id: false });
var Page = mongoose.model('Page', schema);
var p = new Page({ name: 'mongodb.org' });
console.log(p.id); // undefined


option: _id

如果未将一个模式传递给Schema构造函数,默认会为每个模式分配一个_id字段。所分配的类型默认是ObjectId,以与MongoDB的默认行为一致。如果不需要在模式中添加_id,可以禁用此设置项。

这一选项只能在子文档上使用。因为Mongoose无法在不知道其_id的情况下保存文档,这时如果尝试保存文档,则会出现错误。

// default behavior
var schema = new Schema({ name: String });
var Page = mongoose.model('Page', schema);
var p = new Page({ name: 'mongodb.org' });
console.log(p); // { _id: '50341373e894ad16347efe01', name: 'mongodb.org' }

// disabled _id
var childSchema = new Schema({ name: String }, { _id: false });
var parentSchema = new Schema({ children: [childSchema] });

var Model = mongoose.model('Model', parentSchema);

Model.create({ children: [{ name: 'Luke' }] }, function(error, doc) {
  // doc.children[0]._id will be undefined
});


option: minimize

默认情况下,Mongoose会通过删除空对象来“最小化”模式。

var schema = new Schema({ name: String, inventory: {} });
var Character = mongoose.model('Character', schema);

// will store `inventory` field if it is not empty
var frodo = new Character({ name: 'Frodo', inventory: { ringOfPower: 1 }});
Character.findOne({ name: 'Frodo' }, function(err, character) {
  console.log(character); // { name: 'Frodo', inventory: { ringOfPower: 1 }}
});

// will not store `inventory` field if it is empty
var sam = new Character({ name: 'Sam', inventory: {}});
Character.findOne({ name: 'Sam' }, function(err, character) {
  console.log(character); // { name: 'Sam' }
});

可以将minimize选项设置为false,这时可以保存空对象。

var schema = new Schema({ name: String, inventory: {} }, { minimize: false });
var Character = mongoose.model('Character', schema);

// will store `inventory` if empty
var sam = new Character({ name: 'Sam', inventory: {}});
Character.findOne({ name: 'Sam' }, function(err, character) {
  console.log(character); // { name: 'Sam', inventory: {}}
});


option: read

可以在Schema级别设置query#read选项,为Mongoose提供了一种方法,可以将默认的ReadPreferences应用于从模型派生的所有查询。

var schema = new Schema({..}, { read: 'primary' });            // also aliased as 'p'
var schema = new Schema({..}, { read: 'primaryPreferred' });   // aliased as 'pp'
var schema = new Schema({..}, { read: 'secondary' });          // aliased as 's'
var schema = new Schema({..}, { read: 'secondaryPreferred' }); // aliased as 'sp'
var schema = new Schema({..}, { read: 'nearest' });            // aliased as 'n'


option: writeConcern

可以在Schema级别设置write concern

const schema = new Schema({ name: String }, {
  writeConcern: {
    w: 'majority',
    j: true,
    wtimeout: 1000
  }
});


option: safe

safe是一个类似于writeConcern的遗留选项。特别是,safe: false是未确认写入的简写writeConcern: {w: 0}。应使用writeConcern选项来进行设置。


option: shardKey

当我们使用分片MongoDB架构时,应设置shardKey选项。每个分片集合都有一个分片键,必须在所有插入/更新操作中指定。只需要将此schema选项设置为相同的分片键,就可以完成全部设置。

new Schema({ .. }, { shardKey: { tag: 1, name: 1 }})

请注意,Mongoose不会发送shardcollection命令,你必须自己配置分片。


option: strict

strict选项默认为启用状态,应确保该值传递给模型构造函数,未在模式中指定的值将不会保存到数据库。

var thingSchema = new Schema({..})
var Thing = mongoose.model('Thing', thingSchema);
var thing = new Thing({ iAmNotInTheSchema: true });
thing.save(); // iAmNotInTheSchema is not saved to the db

// set to false..
var thingSchema = new Schema({..}, { strict: false });
var thing = new Thing({ iAmNotInTheSchema: true });
thing.save(); // iAmNotInTheSchema is now saved to the db!!

这也会影响使用doc.set()来设置属性值。

var thingSchema = new Schema({..})
var Thing = mongoose.model('Thing', thingSchema);
var thing = new Thing;
thing.set('iAmNotInTheSchema', true);
thing.save(); // iAmNotInTheSchema is not saved to the db

可以向模型实例的第二个参数传递布尔值来覆盖此值:

var Thing = mongoose.model('Thing');
var thing = new Thing(doc, true);  // enables strict mode
var thing = new Thing(doc, false); // disables strict mode

strict选项也可以设置为"throw",这时会抛出错误而不是丢弃坏数据。

var thingSchema = new Schema({..})
var Thing = mongoose.model('Thing', thingSchema);
var thing = new Thing;
thing.iAmNotInTheSchema = true;
thing.save(); // iAmNotInTheSchema is never saved to the db


option: strictQuery

为了向后兼容,strict选项不适用于查询中的filter参数。

const mySchema = new Schema({ field: Number }, { strict: true });
const MyModel = mongoose.model('Test', mySchema);

// Mongoose will **not** filter out `notInSchema: 1`, despite `strict: true`
MyModel.find({ notInSchema: 1 });

strict选项不适用于更新操作:

// Mongoose will strip out `notInSchema` from the update if `strict` is
// not `false`
MyModel.updateMany({}, { $set: { notInSchema: 1 } });

Mongoose有一个单独的strictQuery选项,可以将filter参数的严格模式切换为查询。

const mySchema = new Schema({ field: Number }, {
  strict: true,
  strictQuery: true // Turn on strict mode for query filters
});
const MyModel = mongoose.model('Test', mySchema);

// Mongoose will strip out `notInSchema: 1` because `strictQuery` is `true`
MyModel.find({ notInSchema: 1 });


option: toJSON

toObject选项完全相同,但仅在调用文档toJSON方法时适用。

var schema = new Schema({ name: String });
schema.path('name').get(function (v) {
  return v + ' is my name';
});
schema.set('toJSON', { getters: true, virtuals: false });
var M = mongoose.model('Person', schema);
var m = new M({ name: 'Max Headroom' });
console.log(m.toObject()); // { _id: 504e0cd7dd992d9be2f20b6f, name: 'Max Headroom' }
console.log(m.toJSON()); // { _id: 504e0cd7dd992d9be2f20b6f, name: 'Max Headroom is my name' }
// since we know toJSON is called whenever a js object is stringified:
console.log(JSON.stringify(m)); // { "_id": "504e0cd7dd992d9be2f20b6f", "name": "Max Headroom is my name" }

所有toJSON/toObject选项参数请参考详细


option: toObject

文档有一个toObject方法,它会将mongoose文档转换为普通的JavaScript对象。此方法接受一些选项参数。我们可以在Schema级别声明这一选项,并将其应用于所有这些模式的文档,而不是在每个文档的基础上应用这些选项。

要在console.log输出中显示所有虚拟属性,请将toObject选项设置为{getters: true}

var schema = new Schema({ name: String });
schema.path('name').get(function (v) {
  return v + ' is my name';
});
schema.set('toObject', { getters: true });
var M = mongoose.model('Person', schema);
var m = new M({ name: 'Max Headroom' });
console.log(m); // { _id: 504e0cd7dd992d9be2f20b6f, name: 'Max Headroom is my name' }


option: typeKey

默认情况下,如果模式中有一个键为"type"的对象,mongoose会将其解释为类型声明。

// Mongoose interprets this as 'loc is a String'
var schema = new Schema({ loc: { type: String, coordinates: [Number] } });

但是,对于像geoJSON这样的应用,'type'属性很重要。如果要控制mongoose使用哪个键来查找类型声明,请设置Schema的'typeKey'选项。

var schema = new Schema({
  // Mongoose interpets this as 'loc is an object with 2 keys, type and coordinates'
  loc: { type: String, coordinates: [Number] },
  // Mongoose interprets this as 'name is a String'
  name: { $type: String }
}, { typeKey: '$type' }); // A '$type' key means this object is a type declaration


option: validateBeforeSave

默认情况下,文档会在保存到数据库执行自动执行验证,这样做是为了防止保存无效文档。如果要手动处理验证,并希望能够保存未通过验证的对象,可以将validateBeforeSave设置为false

var schema = new Schema({ name: String });
schema.set('validateBeforeSave', false);
schema.path('name').validate(function (value) {
    return v != null;
});
var M = mongoose.model('Person', schema);
var m = new M({ name: null });
m.validate(function(err) {
    console.log(err); // Will tell you that null is not allowed.
});
m.save(); // Succeeds despite being invalid


option: versionKey

versionKey是Mongoose首次创建时在每个文档上设置的属性,这个键值包含了文档的内部修订版本号。versionKey选项是一个字符串,默认值为__v。如果这与你的应用相冲突,可以这样配置:

const schema = new Schema({ name: 'string' });
const Thing = mongoose.model('Thing', schema);
const thing = new Thing({ name: 'mongoose v3' });
await thing.save(); // { __v: 0, name: 'mongoose v3' }

// customized versionKey
new Schema({..}, { versionKey: '_somethingElse' })
const Thing = mongoose.model('Thing', schema);
const thing = new Thing({ name: 'mongoose v3' });
thing.save(); // { _somethingElse: 0, name: 'mongoose v3' }

通过将versionKey设置为false,可以禁用文档版本控制。除非你确定这么做是必要的,否则请勿禁用版本控制。

new Schema({..}, { versionKey: false });
const Thing = mongoose.model('Thing', schema);
const thing = new Thing({ name: 'no versioning please' });
thing.save(); // { name: 'no versioning please' }

Mongoose仅会在你调用save()时更新版本号。如果你调用update()findOneAndUpdate()等,则不会进行更新,这种情况下可以使用以下中间件来解决。

schema.pre('findOneAndUpdate', function() {
  const update = this.getUpdate();
  if (update.__v != null) {
    delete update.__v;
  }
  const keys = ['$set', '$setOnInsert'];
  for (const key of keys) {
    if (update[key] != null && update[key].__v != null) {
      delete update[key].__v;
      if (Object.keys(update[key]).length === 0) {
        delete update[key];
      }
    }
  }
  update.$inc = update.$inc || {};
  update.$inc.__v = 1;
});


option: collation

为每个查询和聚合设置默认整理(collation)规则。一个关于collation 的概述

var schema = new Schema({
  name: String
}, { collation: { locale: 'en_US', strength: 1 } });

var MyModel = db.model('MyModel', schema);

MyModel.create([{ name: 'val' }, { name: 'Val' }]).
  then(function() {
    return MyModel.find({ name: 'val' });
  }).
  then(function(docs) {
    // `docs` will contain both docs, because `strength: 1` means
    // MongoDB will ignore case when matching.
  });


option: skipVersioning

skipVersioning允许从版本控制中排除路径(即,即使更新了这些路径,内部版本也不会增加)。除非你确定这么做是必要的,否则不要这样做。 对于子文档,请使用完全路径在父文档中包含此文档。

new Schema({..}, { skipVersioning: { dontVersionMe: true } });
thing.dontVersionMe.push('hey');
thing.save(); // version is not incremented


option: timestamps

如果设置了timestamps,Mongoose会在你的Schame中增加createdAtupdatedAt两个属性,其类型为Date

这两个字段的名称默认为createdAtupdatedAt,自定义字段名可以通过timestamps.createdAttimestamps.updatedAt来设置。

const thingSchema = new Schema({..}, { timestamps: { createdAt: 'created_at' } });
const Thing = mongoose.model('Thing', thingSchema);
const thing = new Thing();
await thing.save(); // `created_at` & `updatedAt` will be included

// With updates, Mongoose will add `updatedAt` to `$set`
await Thing.updateOne({}, { $set: { name: 'Test' } });

// If you set upsert: true, Mongoose will add `created_at` to `$setOnInsert` as well
await Thing.findOneAndUpdate({}, { $set: { name: 'Test2' } });

// Mongoose also adds timestamps to bulkWrite() operations
// See https://mongoosejs.com/docs/api.html#model_Model.bulkWrite
await Thing.bulkWrite([
  insertOne: {
    document: {
      name: 'Jean-Luc Picard',
      ship: 'USS Stargazer'
      // Mongoose will add `created_at` and `updatedAt`
    }
  },
  updateOne: {
    filter: { name: 'Jean-Luc Picard' },
    update: {
      $set: {
        ship: 'USS Enterprise'
        // Mongoose will add `updatedAt`
      }
    }
  }
]);


option: useNestedStrict

update(), updateOne(), updateMany()findOneAndUpdate()等写操作仅会验证顶层schema的严格模式设置。

var childSchema = new Schema({}, { strict: false });
var parentSchema = new Schema({ child: childSchema }, { strict: 'throw' });
var Parent = mongoose.model('Parent', parentSchema);
Parent.update({}, { 'child.name': 'Luke Skywalker' }, function(error) {
  // Error because parentSchema has `strict: throw`, even though
  // `childSchema` has `strict: false`
});

var update = { 'child.name': 'Luke Skywalker' };
var opts = { strict: false };
Parent.update({}, update, opts, function(error) {
  // This works because passing `strict: false` to `update()` overwrites
  // the parent schema.
});

如果将useNestedStrict设置为true,则mongoose将使用子schema的strict选项来强制更新。

var childSchema = new Schema({}, { strict: false });
var parentSchema = new Schema({ child: childSchema },
  { strict: 'throw', useNestedStrict: true });
var Parent = mongoose.model('Parent', parentSchema);
Parent.update({}, { 'child.name': 'Luke Skywalker' }, function(error) {
  // Works!
});


option: selectPopulatedPaths

默认情况,Mongoose会自动select()并填充(populate)跳径,除非显式排除。

const bookSchema = new Schema({
  title: 'String',
  author: { type: 'ObjectId', ref: 'Person' }
});
const Book = mongoose.model('Book', bookSchema);

// By default, Mongoose will add `author` to the below `select()`.
await Book.find().select('title').populate('author');

// In other words, the below query is equivalent to the above
await Book.find().select('title author').populate('author');

如果要默认不选择填充字段,可以在模式中将selectPopulatedPaths设置为false

const bookSchema = new Schema({
  title: 'String',
  author: { type: 'ObjectId', ref: 'Person' }
}, { selectPopulatedPaths: false });
const Book = mongoose.model('Book', bookSchema);

// Because `selectPopulatedPaths` is false, the below doc will **not**
// contain an `author` property.
const doc = await Book.findOne().select('title').populate('author');
由于遗留原因,当单个嵌套模式的子路径中存在验证错误时,Mongoose将记录单个嵌套模式路径中存在验证错误。 例如:


option: storeSubdocValidationError

由于遗留原因,当单个嵌套模式的子路径中存在验证错误时,Mongoose将记录单个嵌套模式路径中存在验证错误。例如:

const childSchema = new Schema({ name: { type: String, required: true } });
const parentSchema = new Schema({ child: childSchema });

const Parent = mongoose.model('Parent', parentSchema);

// Will contain an error for both 'child.name' _and_ 'child'
new Parent({ child: {} }).validateSync().errors;

在子模式上将storeSubdocValidationError设置为false,Mongoose将仅报告父模式的错误。

const childSchema = new Schema({
  name: { type: String, required: true }
}, { storeSubdocValidationError: false }); // <-- set on the child schema
const parentSchema = new Schema({ child: childSchema });

const Parent = mongoose.model('Parent', parentSchema);

// Will only contain an error for 'child.name'
new Parent({ child: {} }).validateSync().errors;


10. 可插件化(Pluggable)

模式是可插件化的,它允许我们将可重用的功能打包到插件中,这些插件可以与社区共享,也可以在项目之间共享。


11. 延伸阅读

要充分利用MongoDB,你需要学习MongoDB模式设计的基础知识。SQL模式设计(第三范式)旨在最大限度地降低存储成本,而MongoDB模式设计则旨在尽可能快地进行常见查询。MongoDB Schema Design的6个经验法则博客系列,是学习快速查询的基本规则的绝佳资源。

在Node.js中,要掌握MongoDB模式设计可以参考MongoDB Node.js驱动程序原作者Christian Kvalheim的《The Little MongoDB Schema Design Book》一书。本书中包含了一系列用例的实现高性能模式,包括电子商务、Wiki和预约预定等。


下一步

现在我们已经介绍了Schemas,接下来我们来看一下SchemaTypes


变更记录

  • [2018-11-16] 基于Mongoose官方文档v5.3.12首次发布