官方原版:http://mongoosejs.com
引用请注明出处和转载请注明出处

纲要

如果你还不了解Mongoose如何工作,请先阅读quickstart章节。如果你是从4.x版本迁移到5.x,请花点时间阅读迁移指南

定义你的schema

在mongoose中一切都是从schema开始的,schema会被映射为mongoDB中的collection,并且定义了collection中documents的形式。

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
    }
});

如果你之后想额外的增加keys,可以使用Schema#add方法。

在我们的blogSchema中每个key对应document中的一个字段并且有一个关联的SchemaType。例如,我们定义的title字段的SchemaType为String类型,dateSchemaType是Date类型。keys也可以定义为嵌套的objects,包含了进一步的键/类型的定义,例如上面的meta属性。

下面是允许的schemaTypes类型:

  • String
  • Number
  • Date
  • Buffer
  • Boolean
  • Mixed
  • ObjectId
  • Array

点击查看更多的SchemaType

Schemas不仅定义了文档和其结构,还定义了model的实例方法静态方法复合索引和属于文档中间件的生命周期钩子函数。

创建一个model

我们需要将我们定义的blogSchema转换成一个model才能工作。我们将blogSchema传给mongoose.model(modelName, schema);

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

实例方法

document是model的实例。document有许多内置的实例方法,我们也可以自定义document的实例方法。

// 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);
};

现在所有的animal实例都拥有findSimilarTypes方法。

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

dog.findSimilarTypes(function(err, dogs) {
    console.log(dogs); // woof
});
  • 重写默认的mongoose document方法可能导致无法预知的结果,详情请点击查看
  • 不要使用箭头函数声明方法,因为箭头函数阻止绑定this,所以你的方法无法访问document,上例将无法正常工作。

静态方法

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);
});

要使用箭头函数声明方法,因为箭头函数阻止绑定this,在上例中使用箭头函数将无法正常工作。

查询助手

你也可以像添加实例方法那样给mongoose的查询添加查询助手函数。查询助手方法可以帮你建立链式查询模式。

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

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

索引

MongoDB支持secondary indexes 。在mongoose中,我们可以在schema的path级别和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中定义的索引。mongoose为每个index顺序执行createIndex,当所有的createIndex执行成功或者发生错误时会触发model上的index事件。虽然这个特性有利于开发,但是建议在生成环境中关闭它,因为创建索引会对性能造成很大的影响。可以通过设置autoIndex选择值为false在schema级别上或者全局关闭自动创建索引。

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 });

当所有index创建成功或者发生错误时会触发model上的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方法。

虚拟属性

document的虚拟属性可以被访问和设置,但是不会被存储到MongoDB中。getters用于格式化和组合字段,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' }
});

假设你想输出一个人的全名,你需要这样做:

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

每次都要连接first name和last name是很麻烦的事情。如果你想要对name做些其他的处理,例如移除符号该怎么办呢?你可以定义一个fullname的虚拟属性的getter,该值不会被保存到MongoDB中。

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

现在你可以调用getter函数获取fullName字段的值:

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

mongoose不会输出虚拟的字段,当你使用toJSON()或者toObject()函数时(或者对document使用JSON.stringify()),可以给toJSON()或者toObject(){ virtuals: true }`获取虚拟字段。

你可以自定义虚拟字段的setter函数,通过虚拟的fullName字段一次性设置first name和last name的值。

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会在其他的validation之前运行。所以即使firstlast字段值必须,上例依然会执行。

只有非虚拟字段可以用于查询和选择的字段,因为虚拟字段不会被存到MongoDB中,所以不能用于查询。

别名

别名属于特殊的虚拟字段,其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' }

选项

schemas有一些可配置的选项可直接传递给构造器或者set

new Schema({..}, options);

// or

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

有效的选项:

* autoIndex
* bufferCommands
* capped
* collection
* id
* _id
* minimize
* read
* shardKey
* strict
* strictQuery
* toJSON
* toObject
* typeKey
* validateBeforeSave
* versionKey
* collation
* skipVersioning
* timeStamps

autoIndex

当应用程序启动时,mongoose会发出createIndex命令自动创建你在schema中定义的索引。mongoose v3版本索引默认在后台创建索引。如果你希望关闭自动创建选择手动创建索引时,设置autoIndex选择值为false,并调用model的ensureIndexes方法。

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

bufferCommands

如果connection断掉,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 });

capped

Mongoose支持设置MongoDB集合大小的上限值。设置capped属性限制MongoDB集合的最大值,单位bytes。

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

如果你想传其他的选项,例如max或者autoIndexId,可以将capped值设置为一个对象。这种情况下,size属性为必传值。

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

collection

Mongoose默认使用utils.tocollectionname方法生成集合的集合名称。该方法会生成复数形式的名称。如果你需要自定义集合名称,可以传入下面的选项。

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

id

mongoose默认为每一个schema分配一个虚拟的id getter,该方法返回document的_id字段,类型为string或者ObjectId(哈希字符串)。如果你的schema不需要id getter,给schema的构造函数传option禁用该功能。

// 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

_id

如果没有禁用,mongoose默认为每一个schema分配_id字段,值为ObjectId类型,与MongoDB默认行为一致。如果你的schema不需要id getter,给schema的构造函数传option禁用该功能。

你只能在sub-document中使用此选项,因为如果没有_id,将document保存到数据库时会报错。

// 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
});

minimize

mongoose默认不存储空对象,实现schemas最小化。

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选项重写默认行为,下例中会存储空对象:

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: {}}
});

read

mongoose允许在schema级别设置查询#读取选项,为我们提供一种将默认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'

每个pref允许使用简单的别名,不必须使用全拼,例如’secondaryPreferred’,防止拼写错误。

读选项允许我们设置tag,指明驱动应该试图读取的replicaSet。更多tag设置详情点击查看。

注意:你可以在连接数据库时,指明驱动的读选项的pref strategy

// pings the replset members periodically to track network latency
var options = { replset: { strategy: 'ping' }};
mongoose.connect(uri, options);

var schema = new Schema({..}, { read: ['nearest', { disk: 'ssd' }] });
mongoose.model('JellyBean', schema);

shardKey

当我们有一个分片的MongoDB架构时,需要使用hardKey(片键)选项。每个分片集合都有一个分片键,它必须存在于所有插入/更新操作中。我们只需要在schema属性中配置shardKey。

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

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

strict

mongoose默认开启strict模式,确保传递给model却没有在schema中声明的字段,不会被保存到数据库中。

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

通过给model的实例传递第二个参数重写该选项。

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,如果保存坏数据到数据库会报错而不是忽略该数据。

请注意,不设置schema的option时,model实例设置任何的未在schema中声明的key/val键值对都会被忽视。

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

strictQuery

为了向后兼容,strict选项在查询时不会用于过滤字段。

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用于查询时过滤字段。

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 });

toJSON

toObject选项相同,但是toJSON方法只能被documents调用

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选项。

toObject

document可以调用toObject方法将mongoose document转换为普通的JavaScript对象。该方法接受一些参数,我们可以声明这些选项并默认应用于所有的schema documents,而不是在每个文档的基础上应用这些选项。

想要在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' }

点击查看这里所有可用的toObject选项。

typeKey

如果schema中的object定义了’type’字段,mongoose默认把它理解为字段类型声明。

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

然而有些应用中例如geoJSON,’type’属性是非常重要的,你可以设置’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

validateBeforeSave

在document保存到数据库前会自动执行验证器,对于验证失败的文档会阻止保存。如果你想手动控制验证并且让验证失败的document也可以保存到数据库,可以设置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

versionKey

mongoose默认在第一次创建文档时为每个document设置versionKey。此值包含了document的内部修订版本号。versionkey选项是一个字符串,用于表示版本控制的路径,默认值为_v。如果与你的应用有冲突,你可以如下例中修改:

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

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

可以设置versionKey值为false,禁用document的版本号。不要禁用版本号除非你知道你正在做什么

collation

为每个查询和聚合设置默认的collation。 这是一个初学者友好的collationcollation总览

skipVersioning

skipversioning允许阻止版本号更新(例如,内部版本号不会增加即使这些路径更新)。不要这样做,除非你知道你在做什么。对于子文档,使用完全限定的路径将版本号包含在父文档中。

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

timestamps

如果设置了timestamps,mongoose会为你的schema分配createAtupdateAt字段,值为Date类型。

默认情况下,两个字段的名称为createAtupdateAt,设置timestamps.createdAttimestamps.updatedAt自定义字段名称。

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

useNestedStrict

mongoose v4版本执行update()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在执行updates时会使用child schema的strict选项的值。

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

Pluggable

schema允许使用插件,这样我们将有用的功能打包成插件,在社区或者项目中实现共享。

下一章 —— SchemaTypes