该文章为网络课 Introduction to MongoDB using the MEAN Stack学习笔记。
1. Mongoose
1.1 简介
是一个在node.js上使用的API。功能上作为一种ODM(Object Document Mapper),可以让程序猿以一种面向对象的方式进行数据的创建和存取,将对象转换为实际存储的BSON document则由mongoose来内部转换。
1.2 Mongoose的四种数据类型
- connection
一些socket对象,这些socket将mongoose连接到了mongoDB的某个database。
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test');
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function() {
// we're connected!
});
连接上了db之后,所有代码在'open'事件的callback里面写。
- schema
一些对document所包含内容的规定,比如,包含的field,每个field的一些性质等等
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 }});
- 每一个key定义了一个对象的property,即对应了document中的一个property
- 每一个value对应了property的类型(SchemaType)
- SchemaType可以是数组,如comment
- SchemaType可以是nested Key/Value 结构,如meta
- model
可以把model看作是面向对象中的Class,它利用了schema进行定义,并且在之后用来构造document。
var Blog = mongoose.model('Blog', blogSchema, 'blogs');
此处定义了一个名为'Blog'并且利用了blogSchema的model, 它所对应数据库中的collection名字叫‘blogs’。注意,当第三个参数省略时,Mongoose automatically looks for the plural version of your model name.
- document
就是一个model的一个实例化对象(instance),对应着数据库中的一个document。
var blog = new Blog({
title: "kevin's blog"
...
});
2. Schema Design
2.1 Rule 1: Store What You Query For
MongoDB的改写和读取单个document的数据很快,但是它不支持join操作,因此,它不能够用一条语句来对不同document对象的数据进行query。因此,在设计schema之初,设计者最好能够以后要在一条query中取得的东西存在一个document之中,即"The way you store data should reflect how you use it in your application"
2.2 Rule 2: Least Cardinality
由于MongoDB的document中可以存array,因此看起来似乎解决了oneToMany的关系,但是,由于MongoDB中对单个文件的长度有限制,因此,这个性质不建议用当所要存储在array中的对象的数量没有上限时的应用。这时候应该将存储denormalize分散开来。
3. 更多
3.1 Index
将document中的某个field进行index来提升利用这个field查询数据时的效率。原理是:
- 默认情况下,当利用某个field对某个document进行查询时,MongoDB会扫描(scan)整个数据库,直到找到包含对应的field的那个文件。
- 当对某个field创建index时,MongoDB会区创建一个数据结构,这个结构类似于HashMap,key是filed,value是对应的document。因此可以很快地利用field找到对应的document。
当需要index的field是个array时,这时候叫做multi-key index
index有两种方式:
- 在filed定义的时候( field level)
- 在schema定义完之后(schema level), 组合索引(compound indexes)只能是这种定义方式。
var animalSchema = new Schema({
name: String,
type: String,
tags: { type: [String], index: true } // field level
});
animalSchema.index({ name: 1, type: -1 }); // schema level
- compund index: 先对name排序,在此基础上再对type排序
- 1表示index order是ascending,-1表示descending
3.2. Virtuals
mongoose可以利用已经在schema中定义的属性(property)来定义一种虚拟属性(property)。之所以是虚拟的,是在于这种属性并不是真实存在document中的,即并不是真实存于数据库中的。这种做是为了方便能够对query结果的定制化。
举个例子:
schema, model, document定义如下:
// define a schemavar
personSchema = new Schema({ name: { first: String, last: String }});
// compile our modelvar
Person = mongoose.model('Person', personSchema);
// create a documentvar
bad = new Person({ name: { first: 'Walter', last: 'White' }});
假设我们需要得到文件bad的full name,一种做法是:
console.log(bad.name.first + ' ' + bad.name.last); // Walter White
利用virtuals,可以对schema定义一个virtual property getter:
personSchema.virtual('name.full').get(function () {
return this.name.first + ' ' + this.name.last;
});
那么在query全名的时候,就可以很方便地直接用bad.name.full
:
console.log('%s is insane', bad.name.full); // Walter White is insane
值得注意的是,如果因为某些需求,需要将document利用
JSON.stringify()
转成JSON string或者利用.toObject()
转成一个plain Object时,default情况下并不能把virtual property转过来,如果想要将其保留过来的话,需要对schema做额外的设置:
schema.set('toObject', {virtuals: true});
schema.set('toJSON', {virtuals: true});
3.3 Custom Setter/Setter
Customer Setter
- 例子1:在将数据存到mongoDB之前,需要做一些处理,比如说对用户注册的邮箱进行小写化。
function toLower (v) {
return v.toLowerCase();
}
var UserSchema = new Schema({
email: {
type: String,
set: toLower //转小写setter
}
});
var User = mongoose.model('User', UserSchema);
var user = new User({email: 'AVENUE@Q.COM'});
console.log(user.email); // 'avenue@q.com'
- 例子2:
对document中的某个property进行改动时,有时候需要连带对其他peroperty做出相应的改变。比如说商品始终有一个美元价priceUSD,当时在对document赋值的时候价格上可以使不同的币种,因此这样对价格的设置需要连带改动priceUSD。
// 此处只列出了schema定义的一部分
...
price: {
amount: {
type: Number,
required: true,
set: function(v) { //转USD setter
this.internal.approximatePriceUSD =
v / (fx()[this.price.currency] || 1);
return v;
}
},
// Only 3 supported currencies for now
currency: {
type: String,
enum: ['USD', 'EUR', 'GBP'],
required: true,
set: function(v) { //转USD setter
this.internal.approximatePriceUSD =
this.price.amount / (fx()[v] || 1);
return v;
}
}
},
...
Customer Getter
当从mongoDB取出数据之后,需要做一些处理,使得document对象的value是处理后的值。
- 例子:
数据库中存的信用卡卡号是完整的,但是当对进行query时,只想让其显示后4位。
function obfuscate (cc) {
return '****-****-****-' + cc.slice(cc.length-4, cc.length);
}
var AccountSchema = new Schema({
creditCardNumber: {
type: String,
get: obfuscate // 模糊化getter
}
});
var Account = mongoose.model('Account', AccountSchema);
Account.findById( someId, function (err, found) {
console.log(found.creditCardNumber); // '****-****-****-1234'
});