定義一個(gè) Schema
Mongoose工作都有開始于Schema。每個(gè)Schema對(duì)應(yīng)(映射)一個(gè)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
}
});
如果你創(chuàng)建了Schema后,又想添加字段,可以使用Schema的add方法
代碼中定義的每個(gè)字段屬性都會(huì)轉(zhuǎn)換成SchemaType.例如我們定了title字段,它將被轉(zhuǎn)換成String 的SchemaType 并且字段date將換成Date SchemaType
字段的設(shè)置可以嵌套,像JOSN格式一樣,比如meta字段
允許定的SchemaTypes 有:
查看更多SchemaTypes 點(diǎn)擊這里.
Schemas不僅可以定義表的結(jié)構(gòu)和屬性,還能定義了文檔實(shí)例方法、靜態(tài)模型方法、復(fù)合索引和中間件.。
創(chuàng)建Model
創(chuàng)建一個(gè)Model,我們必須要用到Schema,我們使用上面的blogSchema 創(chuàng)建一個(gè)Model,使用mongoose.model(modelName,blogSchema)
var Blog = mongoose.model('Blog', blogSchema);
// 繼續(xù)……
實(shí)例方法
Model是一個(gè)文檔結(jié)構(gòu),有許多內(nèi)置的方法。我們也可以自定義實(shí)例方法
// 定義一個(gè)schema
var animalSchema = new Schema({ name: String, type: String });
//給animalSchema 添一個(gè)方法
animalSchema.methods.findSimilarTypes = function(cb) {
return this.model('Animal').find({ type: this.type }, cb);
};
現(xiàn)在所有animalSchema的實(shí)例都有一個(gè)findSimilarTypes的方法
var Animal = mongoose.model('Animal', animalSchema);
var dog = new Animal({ type: 'dog' });
dog.findSimilarTypes(function(err, dogs) {
console.log(dogs); //查找 type是dog的表
});
如果重寫Mongoose的文檔內(nèi)置方法可能會(huì)造成不可預(yù)料的錯(cuò),點(diǎn)擊這里查看更多詳情
上面的實(shí)例使用了
Schema.methods對(duì)創(chuàng)建保存一個(gè)實(shí)例方法,點(diǎn)擊這里查看Schema.method()的詳情細(xì)介紹不要使用es6的箭頭方法,關(guān)于es6簡(jiǎn)頭方法的使用請(qǐng)自行查找
靜態(tài)方法
向model添加一個(gè)靜態(tài)方法也非常簡(jiǎn)單,我們繼使用animalSchema:
// 添加靜態(tài)方法
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);
});
查詢助手
你可以定義查詢助手方法,類擬實(shí)例方法,但是主要是用于mongoose的查詢。查詢助手方法允許可以鏈?zhǔn)绞褂?例如:
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 支持二級(jí)索引. 我們可以使用mongoose在Schema 定義這些索引,可以使用以下兩種方法:
//在Schema直接定義
var animalSchema = new Schema({
name: String,
type: String,
tags: { type: [String], index: true }
});
//在Schema對(duì)象方法中添加
animalSchema.index({ name: 1, type: -1 });
當(dāng)你的應(yīng)用開始運(yùn)行時(shí),Mongoose會(huì)為Schema中定義的每一個(gè)索引自動(dòng)創(chuàng)建索引,這個(gè)創(chuàng)建是依次的進(jìn)行的,不是同時(shí)的時(shí)的。當(dāng)所有索引創(chuàng)建完成時(shí)或出錯(cuò)誤會(huì)在model上發(fā)送一個(gè)Index事件。雖然對(duì)開發(fā)者來說創(chuàng)建索引有利于開發(fā),但是生產(chǎn)建不要使用索引,因?yàn)闀?huì)對(duì)性能造成影響??梢越眯┕δ?。使用如下代碼
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 });
開發(fā)時(shí)使用索引,可以使用以下方式監(jiān)聽I(yíng)ndex事件
animalSchema.index({ _id: 1 }, { sparse: true });
var Animal = mongoose.model('Animal', animalSchema);
Animal.on('index', function(error) {
console.log(error.message);
});
詳細(xì)請(qǐng)查看 Model#ensureIndexes 方法。
虛擬(Virtuals)
Virtuals能獲取和設(shè)置的文檔屬性,但不能持久化到MongoDB。getter用于格式化或組合字段,而setter用于將單個(gè)值分解為多個(gè)值進(jìn)行存儲(chǔ)。
var personSchema = new Schema({
name: {
first: String,
last: String
}
});
var Person = mongoose.model('Person', personSchema);
var axl = new Person({
name: { first: 'Axl', last: 'Rose' }
});
你可以這樣使用屬性
console.log(axl.name.first + ' ' + axl.name.last);
每次使用上面將name.first與name.last鏈接起來非常麻煩,如果你想對(duì)name做一些特殊的處理,你可以使用Virtuals的get來處理。虛擬屬性的getter允許您定義一個(gè)不會(huì)持久化到MongoDB的fullName屬性。
personSchema.virtual('fullName').get(function () {
return this.name.first + ' ' + this.name.last;
});
//使用時(shí),跟es6的get set有點(diǎn)相似
console.log(axl.fullName);
如果你使用toJSON()或toObject()時(shí),mongoose默認(rèn)沒有包含虛擬屬性的;這包括在Mongoose文檔上調(diào)用JSON.stringify()的輸出,因?yàn)?code>JSON.stringify()調(diào)用toJSON()。將{virtuals: true}傳遞給toObject()或toJSON()。
你還可以為fullName設(shè)置一個(gè)set方法
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"
在其他驗(yàn)證之前應(yīng)用虛擬屬性設(shè)置器。因此,即使需要姓和名字段,上面的示例仍然有效。虛擬屬性不能作為字段查詢條件,因?yàn)閿?shù)據(jù)庫不存在該字段的
別名(Aliases)
別名是一種特殊的虛擬類型,getter和setter在其中無縫地獲取和設(shè)置另一個(gè)屬性。這對(duì)于節(jié)省網(wǎng)絡(luò)帶寬非常方便,因此可以將存儲(chǔ)在數(shù)據(jù)庫中的短屬性名稱轉(zhuǎn)換為更長(zhǎng)的名稱,以提高代碼的可讀性。
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' }
還可以在嵌套路徑上聲明別名。使用嵌套模式和子文檔更容易,但是也可以內(nèi)聯(lián)聲明嵌套路徑別名,只要使用完整的嵌套路徑嵌套即可
const childSchema = new Schema({
n: {
type: String,
alias: 'name'
}
}, { _id: false });
const parentSchema = new Schema({
// If in a child schema, alias doesn't need to include the full nested path
c: childSchema,
name: {
f: {
type: String,
// Alias needs to include the full nested path if declared inline
alias: 'name.first'
}
}
});
選項(xiàng)
Schemas中有些一些配置選項(xiàng),可直傳給構(gòu)造方法,也可以用set設(shè)置,如下:
new Schema({..}, options);
// 或
var schema = new Schema({..});
schema.set(option, value);
可設(shè)置的選項(xiàng)目有:
- [autoIndex](#option: autoIndex)
- [bufferCommands](#option: bufferCommands)
- capped
- collection
- id
- _id
- minimize
- read
- writeConcern
- safe
- shardKey
- strict
- strictQuery
- toJSON
- toObject
- typeKey
- validateBeforeSave
- versionKey
- collation
- skipVersioning
- timestamps
- selectPopulatedPaths
- storeSubdocValidationError
option: autoIndex
這個(gè)是否創(chuàng)建索引,默認(rèn)情況下會(huì)開啟這一選項(xiàng),在開發(fā)和測(cè)試的時(shí)候還是很有用的。但在生產(chǎn)會(huì)產(chǎn)生比較大的負(fù)載,生產(chǎn)上建議關(guān)閉此選項(xiàng),關(guān)閉方式如下:
const schema = new Schema({..}, { autoIndex: false });
const Clock = mongoose.model('Clock', schema);
Clock.ensureIndexes(callback);
這個(gè)選項(xiàng)默認(rèn)是開啟的,你可以關(guān)閉,使用mongoose.use('autoIndex', false);
option: autoCreate
在Mongoose構(gòu)建索引之前,如果autoCreate設(shè)置為true,它調(diào)用Model.createCollection()在MongoDB中創(chuàng)建底層集合。調(diào)用createCollection()根據(jù)collation選項(xiàng)設(shè)置集合的默認(rèn)排序規(guī)則,如果設(shè)置了capped模式選項(xiàng),則將表建立為一個(gè)有上限的表。與autoIndex一樣,將autoCreate設(shè)置為true有助于開發(fā)和測(cè)試環(huán)境。
不幸的是,createCollection()不能更改現(xiàn)有集合。例如,如果您將capped: 1024添加到您的模式中,而現(xiàn)有的集合沒有被覆蓋,createCollection()將拋出一個(gè)錯(cuò)誤。通常,對(duì)于生產(chǎn)環(huán)境,autoCreate應(yīng)該為false。
option: bufferCommands
這個(gè)選項(xiàng)主是當(dāng)mongoose 中斷了鏈接會(huì)自動(dòng)重新鏈接,直到驅(qū)動(dòng)程序重新連接為止,禁用請(qǐng)?jiān)O(shè)置為false
var schema = new Schema({..}, { bufferCommands: false });
//全局設(shè)置
mongoose.set('bufferCommands', true);
option: capped
設(shè)置Mongodb表大小,單字為字節(jié)
new Schema({..}, { capped: 1024 });
如查你要對(duì)該屬性,設(shè)置max與autoIndexId屬性,那么你可以傳一個(gè)對(duì)象,但該對(duì)象一定要包含size屬性
new Schema({..}, { capped: { size: 1024, max: 1000, autoIndexId: true } });
option: collection
Mongodb表名,默認(rèn)會(huì)生一個(gè)表名,但是最后此項(xiàng)不要用默認(rèn),最好給表設(shè)個(gè)名字
var dataSchema = new Schema({..}, { collection: 'data' });
option: id
是否給Schema添加一個(gè)虛擬id
// 默認(rèn)情況下
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' }
// 禁用_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
});
可存數(shù)據(jù)的時(shí)候沒有_id是沒無保存數(shù)據(jù)的,最好不要禁用此功能
option: minimize
是否刪除空對(duì)象,以此來減小表所占的大小
const schema = new Schema({ name: String, inventory: {} });
const Character = mongoose.model('Character', schema);
// 如果“inventory”字段不是空的,將會(huì)存儲(chǔ)它
const frodo = new Character({ name: 'Frodo', inventory: { ringOfPower: 1 }});
await frodo.save();
let doc = await Character.findOne({ name: 'Frodo' }).lean();
doc.inventory; // { ringOfPower: 1 }
// 如果“inventory”字段是空的,將不會(huì)存儲(chǔ)它
const sam = new Character({ name: 'Sam', inventory: {}});
await sam.save();
doc = await Character.findOne({ name: 'Sam' }).lean();
doc.inventory; // undefined
可以通過以下代碼設(shè)置minimize屬
const schema = new Schema({ name: String, inventory: {} }, { minimize: false });
const Character = mongoose.model('Character', schema);
// 如果nventory是空對(duì)象時(shí),會(huì)存?zhèn)€空對(duì)象進(jìn)去
const sam = new Character({ name: 'Sam', inventory: {} });
await sam.save();
doc = await Character.findOne({ name: 'Sam' }).lean();
doc.inventory; // {}
判斷對(duì)象是否是空對(duì)象可以使用$isEmpty()
const sam = new Character({ name: 'Sam', inventory: {} });
sam.$isEmpty('inventory'); // true
sam.inventory.barrowBlade = 1;
sam.$isEmpty('inventory'); // false
option: read(暫時(shí)還不是很明白此項(xiàng)目的作用,后期弄懂再補(bǔ))
這個(gè)選項(xiàng)是給Schema起一個(gè)別名,配合### readConcern()使用
var schema = new Schema({..}, { read: 'primary' }); // 可以用別名 'p'
var schema = new Schema({..}, { read: 'primaryPreferred' }); // 可以用別名 'pp'
var schema = new Schema({..}, { read: 'secondary' }); // 可以用別名 's'
var schema = new Schema({..}, { read: 'secondaryPreferred' }); // 可以用別名 'sp'
var schema = new Schema({..}, { read: 'nearest' }); // 可以用別名 'n'
option: writeConcern
設(shè)置mongodb寫入策略(WriteConcern),具體說明請(qǐng)自行查找mongodb
const schema = new Schema({ name: String }, {
writeConcern: {
w: 'majority',
j: true,
wtimeout: 1000
}
});
option: safe
safe是一個(gè)與writeConcern類似的遺留選項(xiàng)。特別是,safe: false是未確認(rèn)的寫入writeConcern: {w: 0}的縮寫。而是使用writeConcern選項(xiàng)。
option: shardKey
設(shè)置分片的時(shí)候用到;分片必須自行配置,詳情請(qǐng)自行查看mongodb文檔
new Schema({ .. }, { shardKey: { tag: 1, name: 1 }})
option: strict(嚴(yán)格模式)
這個(gè)選是默認(rèn)開啟的,開啟這個(gè)選項(xiàng),如果你的Schema沒有這個(gè)指定的字段,那么mongoose將不保存到數(shù)據(jù)庫中(建議開啟這個(gè)選項(xiàng))
var thingSchema = new Schema({..})
var Thing = mongoose.model('Thing', thingSchema);
var thing = new Thing({ iAmNotInTheSchema: true });
thing.save(); // 如查開啟將不會(huì)保存到數(shù)據(jù)庫
// set to false..
var thingSchema = new Schema({..}, { strict: false });
var thing = new Thing({ iAmNotInTheSchema: true });
thing.save(); // 關(guān)閉會(huì)保存數(shù)據(jù)到數(shù)據(jù)庫
也可以通過以下方式開啟/關(guān)閉strict
var Thing = mongoose.model('Thing');
var thing = new Thing(doc, true); // 第二個(gè)參數(shù)就strict的值,strict設(shè)為true
var thing = new Thing(doc, false); // 第二個(gè)參數(shù)就strict的值,strict設(shè)為false
可以將“strict”設(shè)置為“throw”,拋出錯(cuò)誤會(huì),而不是過濾沒有的數(shù)據(jù)
*注意,如查實(shí)例不存在的話,你怎么樣設(shè)置這個(gè)參數(shù)都是不起作用的
var thingSchema = new Schema({..})
var Thing = mongoose.model('Thing', thingSchema);
var thing = new Thing;
thing.iAmNotInTheSchema = true;
thing.save(); // iAmNotInTheSchema 數(shù)據(jù)不會(huì)被保存
字段不向后兼容,strict選項(xiàng)不適用查詢時(shí)過濾在Schema不存在的數(shù)據(jù)
const mySchema = new Schema({ field: Number }, { strict: true });
const MyModel = mongoose.model('Test', mySchema);
// mongoose不會(huì)過濾notInSchema,因?yàn)閟trict對(duì)于查詢是不起作用的
MyModel.find({ notInSchema: 1 });
strict選項(xiàng)支持更新數(shù)據(jù)
MyModel.updateMany({}, { $set: { notInSchema: 1 } });
option: strictQuery
該字段與上文的strict相對(duì)應(yīng),這個(gè)字段是在查詢的時(shí)候過濾Schema沒有的字段查詢數(shù)據(jù)
const mySchema = new Schema({ field: Number }, {
strict: true,
strictQuery: true // 打開查詢過濾模式(嚴(yán)格模式)
});
const MyModel = mongoose.model('Test', mySchema);
// Mongoose會(huì)過濾掉'{ notInSchema: 1 }'因已經(jīng)打開查詢過濾模式,此條件無效
MyModel.find({ notInSchema: 1 });
option: toJSON
與toObject選項(xiàng)完全相同,但僅在調(diào)用documents 對(duì)象的toJSON方法時(shí)才有用(實(shí)話說,沒什么大卵用)
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' }
// 只要js對(duì)象被轉(zhuǎn)為字符串,就會(huì)調(diào)用toJSON
console.log(JSON.stringify(m)); // { "_id": "504e0cd7dd992d9be2f20b6f", "name": "Max Headroom is my name" }
option: toObject
documents 有一個(gè)toObject方法,它將mongoose文檔轉(zhuǎn)換為一個(gè)普通的javascript對(duì)象。此方法接受幾個(gè)選項(xiàng)。我們可以在這里聲明這些選項(xiàng),并在缺省情況下將其應(yīng)用于所有這些模式文檔,而不是根據(jù)每個(gè)文檔應(yīng)用這些選項(xiàng)
要讓所有virtuals對(duì)象都顯示在console.log輸出中,請(qǐng)將toObject選項(xiàng)設(shè)置為{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' }
option: typeKey
在默認(rèn)的情況下,如果你在Schema設(shè)置了一個(gè)字段,mongoose將認(rèn)為你這是類型聲明
// Mongoose 將認(rèn)為loc是一個(gè)字符串
var schema = new Schema({ loc: { type: String, coordinates: [Number] } });
如果你需要type是一個(gè)字段,而不是作為字段聲明的類型,那么你可以設(shè)置這個(gè)參數(shù)
var schema = new Schema({
// Mongoose將認(rèn)為loc是一個(gè)對(duì)象,而不是一個(gè)字符串
loc: { type: String, coordinates: [Number] },
// Mongoose將認(rèn)為name是一個(gè)字符串,而不是一個(gè)對(duì)象
name: { $type: String }
}, { typeKey: '$type' }); // 用$type替換type
option: validateBeforeSave
默認(rèn)情況下,文檔在保存到數(shù)據(jù)庫之前會(huì)自動(dòng)驗(yàn)證。這是為了防止保存無效的數(shù)據(jù)。如果希望手動(dòng)處理驗(yàn)證,并能夠保存沒有通過驗(yàn)證的數(shù)據(jù),可以將validatebeforeave設(shè)置為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); // 不允許為空
});
m.save(); // 成功保存
option: versionKey
versionKey,在你最開始創(chuàng)建文檔插入數(shù)據(jù)時(shí)已經(jīng)創(chuàng)建了,一個(gè)記更改,添加的版本號(hào),默認(rèn)的字段是__v,如果字段跟你的業(yè)務(wù)邏輯有沖突,你可以像下面一樣寫
const schema = new Schema({ name: 'string' });
const Thing = mongoose.model('Thing', schema);
const thing = new Thing({ name: 'mongoose v3' });
await thing.save(); // { __v: 0, name: 'mongoose v3' }
// customized versionKey
new Schema({..}, { versionKey: '_somethingElse' })
const Thing = mongoose.model('Thing', schema);
const thing = new Thing({ name: 'mongoose v3' });
thing.save(); // { _somethingElse: 0, name: 'mongoose v3' }
版本號(hào)控制不是一個(gè)很可靠的解決并發(fā)的方案.
Note that Mongoose versioning is not a full optimistic concurrency solution. Use mongoose-update-if-currentfor OCC support. Mongoose versioning only operates on arrays:
還沒有寫完,待更新……