Schemas
定义一个schema
Mongoose 的一切始于 Schema。每个 schema 都会映射到一个 MongoDB collection ,并定义这个collection里的文档的构成。
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 方法。
document 里每个属性的类型都会被转换为 在 blogSchema
里定义对应的 SchemaType。
例如 title
属性会被转换为 SchemaType String,
而 date
属性会被转换为 SchemaType Date
。
还可以像上面 meta
属性,更详细地指定嵌套在里面的属性类型。
允许使用的 SchemaTypes 有:
- String
- Number
- Date
- Buffer
- Boolean
- Mixed
- ObjectId
- Array
更多关于 SchemaTypes
Schema的功能不只是定义文档结构和属性类型,它可以定义——
- document 的 instance methods
- model 的 static Model methods
- 复合索引
- 文档的生命周期钩子,也成为中间件
创建一个 model
我们要把 schema 转换为一个 Model,
使用 mongoose.model(modelName, schema)
函数:
var Blog = mongoose.model('Blog', blogSchema);
// ready to go!
实例方法(method)
documents 是 Models
的实例。 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 的默认方法会造成无法预料的结果,相关链接。
- 不要在自定义方法中使用 ES6 箭头函数,会造成 this 指向错误。
静态方法(static)
添加 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);
});
同样不要在静态方法中使用 ES6 箭头函数
查询助手(query helper)
查询助手作用于 query 实例,方便你自定义拓展你的 链式查询
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);
});
索引(index)
MongoDB 支持 secondary indexes.
在 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 会自动调用 createIndex
初始化你定义的索引。
Mongoose 顺序处理每一个 createIndex
,然后在model触发 'index' 事件。
While nice for development, it is recommended this behavior be disabled
in production since index creation can cause a significant performance
impact.
Disable the behavior by setting the autoIndex
option of your schema to false
, or
globally on the connection by setting the option autoIndex
to 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
虚拟值(Virtual)
Virtuals 是 document 的属性,但是不会被保存到 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' }
});
如果你要log出全名,可以这么做:
console.log(axl.name.first + ' ' + axl.name.last); // Axl Rose
但是每次都这么拼接实在太麻烦了,
推荐你使用virtual property getter,
这个方法允许你定义一个 fullName
属性,但不必保存到数据库。
personSchema.virtual('fullName').get(function () {
return this.name.first + ' ' + this.name.last;
});
现在, mongoose 可以调用 getter 函数访问
fullName
属性:
console.log(axl.fullName); // Axl Rose
如果对 document 使用 toJSON()
或 toObject()
,默认不包括虚拟值,
你需要额外向 toObject() 或者 toJSON()
传入参数 { virtuals: true }
。
你也可以设定虚拟值的 setter ,下例中,当你赋值到虚拟值时,它可以自动拆分到其他属性:
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"
再次强调,虚拟值不能用于查询和字段选择,因为虚拟值不储存于 MongoDB。
别名(Alias)
Aliase 是一种特殊的虚拟值,它的 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);
Valid options:
- autoIndex
- bufferCommands
- capped
- collection
- id
- _id
- minimize
- read
- shardKey
- strict
- strictQuery
- toJSON
- toObject
- typeKey
- validateBeforeSave
- versionKey
- collation
- skipVersioning
- timestamps
- useNestedStrict
option: autoIndex
应用启动时,Mongoose 自动发送
createIndex
指令,
schema 里的每个 index 都会被创建。
如果你需要关闭自动创建功能或者需要在创建后进行一系列操作,
可以把 autoIndex
设为 false
,
然后对 model 调用 ensureIndexes:
var schema = new Schema({..}, { autoIndex: false });
var Clock = mongoose.model('Clock', schema);
Clock.ensureIndexes(callback);
option: bufferCommands
By default, mongoose buffers commands when the connection goes down until
the driver manages to reconnect. To disable buffering, set bufferCommands
to false.
var schema = new Schema({..}, { bufferCommands: false });
The schema bufferCommands
option overrides the global bufferCommands
option.
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
collections。 要从底层把 collection 设定为 capped
(封顶),
可以把 collection 的最大容量设定到 capped
选项(单位bytes)。
new Schema({..}, { capped: 1024 });
The capped
option may also be set to an object if you want to pass
additional options like max
or autoIndexId.
这个情况下你需要显式传入必要值 size
。
new Schema({..}, { capped: { size: 1024, max: 1000, autoIndexId: true } });
option: collection
Mongoose 通过 utils.toCollectionName 方法 默认生成 collection 的名称(生成 model 名称的复数形式)。 设置这个选项可以自定义名称。
var dataSchema = new Schema({..}, { collection: 'data' });
option: id
Mongoose 会默认生成一个虚拟值 id
,指向文档的 _id
字段。
如果你不需要 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'
// 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
Mongoose 默认给你的 Schema 赋值一个 _id
。
这个值的类型是 ObjectId,这与MongoDB的默认表现一致。
如果你不需要 _id
,可以通过这个选项禁用此功能。
此选项只能用于 subdocument。 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
,Mongoose 将保存空对象。
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
Allows setting query#read options at the schema level, providing us a way to apply default ReadPreferences to all queries derived from a model.
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'
The alias of each pref is also permitted so instead of having to type out 'secondaryPreferred' and getting the spelling wrong, we can simply pass 'sp'.
The read option also allows us to specify tag sets. These tell the driver from which members of the replica-set it should attempt to read. Read more about tag sets here and here.
NOTE: you may also specify the driver read pref strategy option when connecting:
// 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);
option: shardKey
分片相关
The shardKey
option is used when we have a sharded MongoDB architecture.
Each sharded collection is given a shard key which must be present in all
insert/update operations. We just need to set this schema option to the same
shard key and we’ll be all set.
new Schema({ .. }, { shardKey: { tag: 1, name: 1 }})
Note that Mongoose does not send the shardcollection
command for you. You
must configure your shards yourself.
option: strict
Strict 选项默认为 true,这意味着你不能 save
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
The strict
option may also be set to "throw"
which will cause errors
to be produced instead of dropping the bad data.
NOTE: Any key/val set on the instance that does not exist in your schema is always ignored, regardless of schema option.
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
For backwards compatibility, the strict
option does not apply to
the filter
parameter for queries.
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 });
The strict
option does apply to updates.
// Mongoose will strip out `notInSchema` from the update if `strict` is
// not `false`
MyModel.updateMany({}, { $set: { notInSchema: 1 } });
Mongoose has a separate strictQuery
option to toggle strict mode for
the filter
parameter to queries.
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
Exactly the same as the toObject option but only applies when
the documents toJSON
method is called.
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" }
To see all available toJSON/toObject
options, read this.
option: toObject
Documents 的 toObject 方法可以把文档转换成一个 plain javascript object (也就是去掉里面的方法)。 这是一个可以接收多个参数的方法,我们可以在 schemas 定义这些参数。
例如要打印出虚拟值,可以向 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
选项请看这里。
option: typeKey
By default, if you have an object with key 'type' in your schema, mongoose will interpret it as a type declaration.
// Mongoose interprets this as 'loc is a String'
var schema = new Schema({ loc: { type: String, coordinates: [Number] } });
However, for applications like geoJSON, the 'type' property is important. If you want to control which key mongoose uses to find type declarations, set the 'typeKey' schema option.
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
By default, documents are automatically validated before they are saved to
the database. This is to prevent saving an invalid document. If you want to
handle validation manually, and be able to save objects which don't pass
validation, you can set validateBeforeSave
to 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
。如果这个值与你的计划冲突,你可以设定为其他名称:
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' }
你也可以赋值为 false
禁用 versionKey
。
你不应该随便禁用这个功能,除非你清楚知道这有什么影响。
new Schema({..}, { versionKey: false });
var Thing = mongoose.model('Thing', schema);
var thing = new Thing({ name: 'no versioning please' });
thing.save(); // { name: 'no versioning please' }
option: collation
为 查询(query)和 聚合(aggregation)设置 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
allows excluding paths from versioning (i.e., the internal
revision will not be incremented even if these paths are updated). DO NOT
do this unless you know what you're doing. For subdocuments, include this
on the parent document using the fully qualified path.
new Schema({..}, { skipVersioning: { dontVersionMe: true } });
thing.dontVersionMe.push('hey');
thing.save(); // version is not incremented
option: timestamps
如果设置了 timestamps
选项, mongoose 会在你的 schema 自动添加 createdAt
和 updatedAt
字段,
其类型为 Date。
这两个字段的默认名是 createdAt
和 updatedAt
, 你可以通过设定
timestamps.createdAt
和 timestamps.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
option: useNestedStrict
在 mongoose4 中,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) {
// 报错!原因是父Schema设定为`strict: throw`,但是因为只检查顶层,导致
// 子Schema的 `strict: false` 遭到无情忽视
});
var update = { 'child.name': 'Luke Skywalker' };
var opts = { strict: false };
Parent.update({}, update, opts, function(error) {
// 这样可以,因为重写了父Schema的 strict 选项
});
如果你把 useNestedStrict
设为 true,mongoose 就不会忽略嵌套的 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!
});
Pluggable
Schemas 是 pluggable(可扩展的), 我们可以打包插件分享到社区,或者复用于自己的项目。
下一步
这章我们介绍了 Schemas
,下一个章节将会介绍
SchemaTypes。