mongoose學習筆記一
名詞解釋
Schema: 一種以檔案形式儲存的資料庫模型骨架,不具備資料庫的操作能力
Model: 由Schema編譯而成的假想(fancy)構造器,具有抽象屬性和行為。Model的每一個例項(instance)就是一個document
。document
可以儲存到資料庫和從資料庫返回。
Instance: 由Model建立的例項。
概念解析
SQL術語/概念 | MongoDB術語/概念 | 解釋/說明 |
---|---|---|
database | database | |
table | collection | 資料庫表/集合 |
row | document | 資料記錄行/文件 |
column | field | 資料欄位/域 |
index | index | 索引 |
table joins | 表連線,MongoDB不支援 | |
primary key | primary key | 主鍵,MongoDB自動將_id欄位設定為主鍵 |
定義Schema
mongoose中任何任何事物都是從Schema開始的。每一個Schema對應MongoDB中的一個集合(collection)。Schema中定義了集合中文件(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中新增鍵,可以使用Schema#add
方法。
創造一個model
為了使用schema定義,我們需要轉換blogSchema
Model
。使用mongoose.model(modelName, schema)
。
var BlogModel = mongoose.model('Blog', blogSchema);
// 開始吧!
例項方法
Model
的例項是document
。例項有很多內建的方法,我們也可以給例項自定義方法。
var animalSchema = new Schema({ name: String, type: String });
animalSchema.methods.findSimilarTypes = function (cb) {
return this.model('Animal').find({ type: this.type }, cb);
}
現在所有的動物例項有findSimilarTypes
方法。
var AnimalModel = mongoose.model('Animal', animalSechema);
var dog = new AnimalModel({ type: 'dog' });
dog.findSimilarTypes(function (err, dogs) {
console.log(dogs); // woof
});
重寫一個預設的例項方法可能會導致不期待的結果。
Statics
給Model
新增一個靜態方法也是簡單的。
animalSchema.statics.findByName = function (name, cb) {
this.find({ name: new RegExp(name, 'i') }, cb);
}
var AnimalModel = mongoose.model('Animal', animalSchema);
AnimalModel.findByName('fido', function (err, animals) {
console.log(animals);
});
methods和statics的區別
區別就是一個給Model
新增方法(statics
),一個給例項新增方法(methods
)。下面是stackOverflow的兩個答案。
索引
MongoDB
支援二級索引,定義索引有兩種方式
- 路徑級別
- schema級別
var animalSchema = new Schema({
name: String,
type: String,
tags: { type: [String], index: true } // field level
});
animalSchema.index({ name: 1, type: -1 }); // schema level, 1是正序,-1是倒序
如果要建立複合索引的話,在schema級別建立是必要的。
索引或者複合索引能讓搜尋更加高效,預設索引就是主鍵索引ObjectId,屬性名為_id
。
資料庫中主要的就是CRUD操作,建立索引可以提高查詢速度。但是過多的索引會降低CUD操作。深度好文如下
虛擬屬性
Schema
中如果定義了虛擬屬性,那麼該屬性將不寫入資料庫。寫入資料庫的還是原來的屬性。
// 定義一個schema
var personSchema = new Schema({
name: {
first: String,
last: String
}
});
// 編譯
var Person = mongoose.model('Person', personSchema);
// 創造例項
var bad = new Person({
name: { first: 'Walter', last: 'White' }
});
我們將名字分成名字和姓,如果要得到全名,我們需要
console.log(bad.name.first + ' ' + bad.name.last); // Walter White
這樣無疑是麻煩的,我們可以通過虛擬屬性的getter
來解決這個問題。
personSchema.virtual('name.full').get(function () {
return this.name.first + ' ' + this.name.last;
});
那麼就可以使用bad.name.full
直接呼叫全名了。
反之,如果我們知道虛擬屬性name.full
,通過setter
也可以得到組成name.full
的每一項。
personSchema.virtual('name.full').set(function (name) {
var split = name.split(' ');
this.name.first = split[0];
this.name.last = split[1];
});
...
mad.name.full = 'Breaking Bad';
console.log(mad.name.first); // Breaking
console.log(mad.name.last); // Bad
配置項
schema有一些配置項可以使用,有兩種方式:
new Schema({...}, options)
var schema = new Schema({...}); schema.set(option, value);
有效的配置有:
- autoIndex(預設true)
- capped
- collection
- id
- _id(預設true)
- read
- safe(預設true)
- shardKey
- strict(預設true)
- toJSON
- toObject
- versionKey
- typeKey
- validateBeforeSave
- skipVersioning
- timestamps
- useNestedStrict
- retainKeyOrder
autoIndex–自動索引
應用開始的時候,Mongoose對每一個索引發送一個ensureIndex
的命令。索引預設(_id
)被Mongoose建立。當我們不需要設定索引的時候,就可以通過設定這個選項。
var schema = new Schema({..}, { autoIndex: false });
var Clock = mongoose.model('Clock', schema);
Clock.ensureIndexes(callback);
bufferCommands
似乎是說這個(mongoose buffer)管理在mongoose連線關閉的時候重連,如果取消buffer設定,如下:(存疑)
var schema = new Schema({..}, { bufferCommands: false });
capped–上限設定
如果有資料庫的批量操作,該屬效能限制一次操作的量,例如:
new Schema({...},{capped:1024}); //一次操作上線1024條資料
當然該引數也可是物件,包含size、max、autiIndexId屬性
new Schema({...},{capped:{size:1024,max:100,autoIndexId:true}});
collection–集合名字
在MongDB中預設使用Model的名字作為集合的名字,如過需要自定義集合的名字,可以通過設定這個選項。
var schema = new Schema({...}, {collection: 'yourName'});
id
mongoose分配給每一個schema一個虛擬屬性id,它是一個getter。返回的是_id轉換為字串後的值。如果不需要為schema新增這個getter,可以通過id配置修改。
// 預設行為
var pageSchema = new Schema({ name: String });
var pageModel = mongoose.model('Page', pageSchema);
var p = new pageModel({ name: 'mongodb.org' });
console.log(p.id); // '50341373e894ad16347efe01'
// 禁止id
var pageSchema = new Schema({ name: String }, { id: false } );
var pageModel = mongoose.model('Page', pageSchema);
var p = new pageModel({ name: 'mongodb.org' });
console.log(p.id); // undefined
_id
在一個schema中如果沒有定義_id
域(field),那麼mongoose將會預設分配一個_id
域(field)。型別是ObjectId
。如果不需要使用這個預設的選擇,可以通過設定這個選項。
通過在schema中設定這個欄位可以阻止生成mongoose獲得_id。但是在插入的時候仍然會生成_id
。設定這個欄位之後,如果再使用Schema.set('_id', false)
將無效。
// 預設行為
var pageSchema = new Schema({ name: String });
var pageModel = mongoose.model('Page', pageSchema);
var p = new pageModel({ name: 'mongodb.org' });
console.log(p); // { _id: '50341373e894ad16347efe01', name: 'mongodb.org' }
// 禁用 _id
var pageSchema = new Schema({ name: String }, { _id: false });
// schema構造器設定之後,不要再像下面這樣設定
// var schema = new Schema({ name: String });
// schema.set('_id', false);
var PageModel = mongoose.model('Page', pageSchema);
var p = new pageModel({ name: 'mongodb.org' });
console.log(p); // { name: 'mongodb.org' }
// 當插入的時候,MongoDB將會建立_id
p.save(function (err) {
if (err) return handleError(err);
pageModel.findById(p, function (err, doc) {
if (err) return handleError(err);
console.log(doc); // { name: 'mongodb.org', _id: '50341373e894ad16347efe12' }
})
})
read
safe
這個配置會在MongoDB所有的操作中起作用。如果設定成true就是在操作的時候要等待返回的MongoDB返回的結果,比如update,要返回影響的條數,才往後執行,如果safe:false,則表示不用等到結果就向後執行了。
預設設定為true能保證所有的錯誤能通過我們寫的回撥函式。我們也能設定其它的安全等級如:
{ j: 1, w: 2, wtimeout: 10000 }
表示如果10秒內寫操作沒有完成,將會超時。
關於j和w,這裡有很好的解釋。
shardKey
需要mongodb做分散式,才會使用該屬性。
strict
預設是enabled,如果例項中的域(field)在schema中不存在,那麼這個域不會被插入到資料庫。
var ThingSchema = new Schema({a:String});
var ThingModel = db.model('Thing',SchemaSchema);
var thing = new Thing({iAmNotInTheThingSchema:true});
thing.save();//iAmNotInTheThingSchema這個屬性將無法被儲存
// 通過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
如果取消嚴格選項,iAmNotInTheThingSchema將會被存入資料庫
var thingSchema = new Schema({..}, { strict: false });
var thing = new Thing({ iAmNotInTheSchema: true });
thing.save(); // iAmNotInTheSchema is now saved to the db!!
該選項也可以在Model級別使用,通過設定第二個引數,例如:
var ThingModel = db.model('Thing');
var thing1 = new ThingModel(doc,true); //啟用嚴格
var thing2 = new ThingModel(doc,false); //禁用嚴格
strict也可以設定為throw
,表示出現問題將會丟擲錯誤而不是拋棄不合適的資料。
注意:
- 不要設定為false除非你有充分的理由。
在mongoose v2裡預設是false。
在例項上設定的任何鍵值對如果再schema中不存在對應的,將會被忽視。
var thingSchema = new Schema({..})
var Thing = mongoose.model('Thing', thingSchema);
var thing = new Thing;
thing.iAmNotInTheSchema = true;
thing.save(); // iAmNotInTheSchema 不會儲存到資料庫。
toJSON
和toObject
類似,選擇這個選項為true後,但是隻有當例項呼叫了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' }
console.log(JSON.stringify(m)); // { "_id": "504e0cd7dd992d9be2f20b6f", "name": "Max Headroom is my name" }
可以看出,配置屬性name對toObject
沒影響,對toJSON
有影響。
toObject
選擇這個選項為true後,預設對這個schema所有的例項都有作用。不需要例項手動呼叫。
ar 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' }
較上面不同的是,沒有virtuals: false
這個設定。
typeKey
在mongoose裡,如果schema裡有個物件,並且這個物件有個type鍵,mongoose將會將這個作為一種型別宣告。
// Mongoose 認為loc欄位的型別是一個字串,而不是有type這個欄位
var schema = new Schema({ loc: { type: String, coordinates: [Number] } });
然而,對於一些應用來說,type欄位是必要的。那麼可以通過typeKey來設定。
var schema = new Schema({
// Mongoose 這時候認為loc欄位有兩個鍵,一個是type,一個是coordinates
loc: { type: String, coordinates: [Number] },
// Mongoose 這時候認為name欄位的型別是字串。
name: { $type: String }
}, { typeKey: '$type' });
// '$type'鍵意味著這是一個型別宣告,而不是預設的type
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); // 將會告訴你null不被允許
});
m.save(); // 儘管資料無效,但是仍然可以儲存。
versionKey
版本鎖設定在每一個文件(document
)上,由mogoose生成。預設的值是__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' }
// 自定義版本鎖
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除非你知道你在做什麼。
skipVersioning
按照這裡的說法,大致是說,加入在一個部落格系統中,一個人所有的評論是一個數組,那麼所有的評論是有索引的,比如某一條評論的body,comments.3.body
,這裡3是索引。假如一個評論者(A)想要修改自己的評論,但是此時另一個評論者(B)刪除(或其他操作)了自己的評論,那麼對A的索引可能會造成變化,此時對A的操作會發生錯誤。
為了改變這個問題,mongoose v3添加了version key配置。無論什麼時候修改一個數組潛在地改變陣列元素位置,這個version key(__V)的值會加1。在where條件中也需要新增__v
條件,如果能通過(陣列索引沒改變),就可以修改,例如:
posts.update({ _id: postId, __v: verionNumber }
, { $set: { 'comments.3.body': updatedText }}
如果在更新之前刪除了評論,那麼就會發生錯誤。
post.save(function (err) {
console.log(err); // Error: No matching document found.
});
timestamps
如果在schema設定這個選項,createdAt和updatedAt域將會被自動新增的文件中。它們預設的型別是Date,預設的名字是createdAt和updatedAt,不過我們可以自己修改。
var thingSchema = new Schema({..}, { timestamps: { createdAt: 'created_at' } });
var Thing = mongoose.model('Thing', thingSchema);
var thing = new Thing();
thing.save(); // created_at & updatedAt將會被包含在文件。
useNestedStrict
在mongoos 4, update()
和findOneAndUpdate()
方法只檢查頂級schema的strict的選項設定。
var childSchema = new Schema({}, { strict: false });
// 這裡parentSchema是topSchema,而childSchema是subSchema。
var parentSchema = new Schema({ child: childSchema }, { strict: 'throw' });
var Parent = mongoose.model('Parent', parentSchema);
Parent.update({}, { 'child.name': 'Luke Skywalker' }, function(error) {
// 發生錯誤因為parentSchema設定了{strict: 'throw'}
// 即使childSchema設定了{strict: false}
});
var update = { 'child.name': 'Luke Skywalker' };
var opts = { strict: false };
Parent.update({}, update, opts, function(error) {
// 這個可以通過因為重寫了parentSchema的strict選項
});
如果設定了useNestedStrict為true,mogoose在更新時使用childSchema的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) {
// 可以更新
});
retainKeyOrder
預設得,mongoose會轉換實體中鍵的順序。比如new Model({ first: 1, second: 2 })
將會在MongoDB中儲存為{ second: 2, first: 1 }
;這帶來了極大的不方便。
Mongoose v4.6.4 有一個retainKeyOrder選項確保mongoose不會改變鍵的順序。