mongoose学习笔记2:Schame
当阅读这篇文章时,如果你对 Mongoose 还不了解,那么建议你花上一点时间阅读一下 Quick Start 或者阅读我写的上一篇文章:mongoose学习笔记1:起步 ,以了解 Mongoose 的基本运作机制。如果你正从 4.x 版本过渡到 5.x 版本,那么也建议你花上一点时间阅读 migration guide 。
定义 Schema
在 Mongoose 中,一切事物都起源于一个 Schema 。每一个 Schema 对应 MongoDB 中的一个集合,并定义了这个集合由哪些形式的字段组成。
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 实例创建后,你可以通过使用 Schema 实例的 add 方法,为 Schema 添加一个字段信息。
blogSchema.add({ abstract: String });
blogSchema
中的每一个键都定义了我们文档中的一个属性,该属性将被转换为与之对应的 SchemaType 。在这个例子中,我们定义了一个 String
类型的 title ,一个 Date
类型的 date 以及含有键值对的一个 Object
类型的 meta 。
SchemaType 可以是以下的值:
- String
- Number
- Date
- Buffer
- Boolean
- Mixed
- ObjectId
- Array
- Decimal128
- Map
Schema 不只是定义了 Model 的结构和属性,同时还定义了 Model 的实例方法、静态方法、复合索引以及 Model 的生命周期钩子(也称作 中间件 )。
创建 Model
要使用我们定义好的 blogSchema ,我们需要将其编译为我们可以使用的 Model 。你只需要将 blogSchema 传递给 mongoose.model 就可以:
var Blog = mongoose.model('Blog', blogSchema);
实例方法
每一个 Model 实例都是一个文档,这些文档有着他们原生的实例方法,当然我们也可以为这些文档定制我们的想要的实例方法:
// 定义一个 Schema
var animalSchema = new Schema({ name: String, type: String });
// 为 animalSchema 的 methods 对象添加一个方法
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 实例方法可能会导致出现不可预知的后果,有关详细信息,可以点击这里查看。
- 上面的实例直接将实例方法定义在 Schema 的 methods 属性上,你也可以通过调用 Schema 的 method 方法实现同样的效果。
- 不用使用ES6的箭头函数,否则你定义的方法将无法访问 Model 实例,上面的实例也就不起作用了。
静态方法
为 Model 添加一个静态方法也是很简单的,我们还是以 animalSchema
为例。
// 将静态方法定义在 Schema 的 statics 对象上
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的箭头函数来定义静态方法。
查询助手
你也可以为 Model 实例添加查询助手,他们看起来有点像实例方法,但不同的是他们用于 Model 查询。查询助手可以让你扩展 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);
});
索引
MongoDB支持辅助索引。在 mongoose 中,可以在 路径级别 或 模式级别 中定义这些索引。创建复合索引时,推荐在模式级别中定义。
var animalSchema = new Schema({
name: String,
type: String,
tags: { type: [String], index: true } // 路径级别
});
animalSchema.index({ name: 1, type: -1 }); // 模式级别
当你的程序启动时,Mongoose 将会自动调用 Schema 中定义的每一个索引的 createIndex
方法。mongoose 会依次调用每个索引的 createIndex
,并在所有的 createIndex
调用完成后,在 Model 上触发一个 "index" 事件。虽然创建索引对于开发来说很方便,但建议在生产环境中禁用这种行为,因为索引的创建可能会对性能产生重大影响。通过将 Schema 的 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 });
当所有的 createIndex 都调用成功 或 出现报错时,mongoose 将会在 Model 上触发一个 "index" 事件。
// 以下代码将导致报错,原因是 MongoDB 的 _id 索引默认
// sparse 为 false
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);
});
虚拟属性Virtuals
虚拟属性是可读写的 Model 的属性,但它不会被保存到 MongoDB 中。你可以通过 虚拟属性的 getter 格式化或组合多个字段,通过 setter 将单个值分解为多个值来进行存储。
// 定义一个 Schema
var personSchema = new Schema({
name: {
first: String,
last: String
}
});
// 编译成 model
var Person = mongoose.model('Person', personSchema);
// 创建一个 model 实例
var axl = new Person({
name: { first: 'Axl', last: 'Rose' }
});
当你需要打印 axl 的全名是,你需要这样:
console.log(axl.name.first + ' ' + axl.name.last); // Axl Rose
但如果每次都需要连接名字和姓氏则会显得很麻烦。如果你想对名字进行一些额外的处理,比如全名需要会是怎么样呢?虚拟属性的 getter 可以让你定义不会被保存到 MongoDB 中的全名属性。
personSchema.virtual('fullName').get(function () {
return this.name.first + ' ' + this.name.last;
});
现在,每次当你获取 fullName
的值时,mongoose 将会调用你定义的 getter 方法。
console.log(axl.fullName); // Axl Rose
默认情况下,当你调用 toJSON()
或 toObject()
时,mongoose 将不会带上虚拟属性(除非你向这两个方法传入参数:{ virtuals: true }
),这也包括当你调用 JSON.stringify()
方法时,也不会带有虚拟属性。
你还可以设置一个自定义的 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'; // 现在 axl.name.first 的值为 William
由于虚拟属性并没有被存储在 MongoDB 中,因此你不能使用虚拟属性来进行查询操作。
别名 Aliases
别名是一种特殊的虚拟属性,可以无缝地获取或者设置另一个属性的值。你可以将存储在 MongoDB 中的短属性名转换为更长的名称,这对于节省网络宽带是非常方便的。
var personSchema = new Schema({
n: {
type: String,
// 定义一个别名
alias: 'name'
}
});
var person = new Person({ name: 'Val' });
console.log(person); // { n: 'Val' }
console.log(person.name); // 'Val'
console.log(person.toObject({ virtuals: true })); // { n: 'Val', name: 'Val' }
下篇再续...