Mongoose裡面最重要的三個概念是Schemas,Models和Documents,另外還有驗證,中介軟體等,前面三個至關重要,這裡像填充Populate
和鑑別器Discriminators
的相關知識就不介紹了,平時基本上不太能用到,具體的可以查閱官方文件。
Mongoose的一切始於Schema
,Schema
定義collection
裡的文件的構成,models
是從Schema
編譯來的建構函式,它們的例項就代表著可以從資料庫儲存和讀取的documents
,從資料庫建立和讀取document
的操作都是通過model
進行的,而儲存時我們是在model
例項上進行的。
var schema = new mongoose.Schema({ name: 'string', size: 'string' });
var Tank = mongoose.model('Tank', schema);
var small = new Tank({ size: 'small' });
small.save(function (err) {
if (err) return handleError(err);
// saved!
})
// or
Tank.create({ size: 'small' }, function (err, small) {
if (err) return handleError(err);
// saved!
})
複製程式碼
模式Schemas
- 如何為models例項(Documents)定義我們自己的方法 雖然douments有很多自帶的例項方法,但我們也可以自定義我們自己的方法,這時需要在schema上定義方法。
var animalSchema = new Schema({ name: String, type: String });
animalSchema.methods.findSimilarTypes = function(cb) {
return this.model('Animal').find({ type: this.type }, cb);
};
var Animal = mongoose.model('Animal', animalSchema);
var dog = new Animal({ type: 'dog' });
dog.findSimilarTypes(function(err, dogs) {
console.log(dogs); // woof
});
複製程式碼
也可以新增靜態方法
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);
});
複製程式碼
2.Schemas有很多的配置選項,可以在構造時或者直接set
new Schema({..}, options);
// or
var schema = new Schema({..});
schema.set(option, value);
複製程式碼
模型Models
查詢
查詢文件可以用model的find,findById,findOne和where這些靜態方法
刪除
remove方法
更新
model的update方法可以修改資料庫中的文件,不過不會把文件返回
findOneAndUpdate方法用來更新單獨一條文件並且返回給應用層
文件Documents
檢索
檢索方法較多,暫不闡述
更新
更新的方法也比較多
-
findById+改變值得操作
Tank.findById(id, function (err, tank) { if (err) return handleError(err); tank.size = 'large';//或者使用tank.set({ size: 'large' }); tank.save(function (err, updatedTank) { if (err) return handleError(err); res.send(updatedTank); }); }); 複製程式碼
-
update
Tank.update({ _id: id }, { $set: { size: 'large' }}, callback); 複製程式碼
-
findByIdAndUpdate 這個方法會返回文件
Tank.findByIdAndUpdate(id, { $set: { size: 'large' }}, { new: true }, function (err, tank) { if (err) return handleError(err); res.send(tank); }); 複製程式碼
-
其他方法如findAndUpdate/Remove查詢並返回最多一個文件
驗證
Document會在被儲存之前驗證
覆蓋
可以用.set( )覆蓋整個文件
Tank.findById(id, function (err, tank) {
if (err) return handleError(err);
// Now `otherTank` is a copy of `tank`
otherTank.set(tank);
});
複製程式碼
查詢queries
Model 的方法中包含查詢條件引數的( find findById count update )都可以按以下兩種方式執行:
-
傳入
callback
引數,操作會被立即執行,查詢結果被傳給回撥函式( callback )。 -
不傳
callback
引數,Query 的一個例項(一個 query 物件)被返回,這個 query 提供了構建查詢器的特殊介面。Query 例項有一個.then()
函式,用法類似 promise。傳callback引數的情況:
var Person = mongoose.model('Person', yourSchema);
// 查詢每個 last name 是 'Ghost' 的 person, select `name` 和 `occupation` 欄位
Person.findOne({ 'name.last': 'Ghost' }, 'name occupation', function (err, person) {
if (err) return handleError(err);
// Prints "Space Ghost is a talk show host".
console.log('%s %s is a %s.', person.name.first, person.name.last,
person.occupation);
});
複製程式碼
不傳callback引數的情況:
// 查詢每個 last name 是 'Ghost' 的 person
var query = Person.findOne({ 'name.last': 'Ghost' });
// select `name` 和 `occupation` 欄位
query.select('name occupation');
// 然後執行查詢
query.exec(function (err, person) {
if (err) return handleError(err);
// Prints "Space Ghost is a talk show host."
console.log('%s %s is a %s.', person.name.first, person.name.last,
person.occupation);
});
複製程式碼
模式型別SchemaTypes
有一下SchemaTypes:
-
String
-
Number
-
Date
-
Buffer
-
Boolean
-
Mixed
-
ObjectId
-
Array
-
Decimal128 可以宣告schema type為某一種type,或者賦值一個含有type屬性的物件
var schema1 = new Schema({ test: String // `test` is a path of type String }); var schema2 = new Schema({ test: { type: String } // `test` is a path of type string }); 複製程式碼
除了type屬性,以下有一些全部type可用的選項和一些限定部分type使用的選項
全部可用
required
: 布林值或函式 如果值為真,為此屬性新增 required 驗證器default
: 任何值或函式 設定此路徑預設值。如果是函式,函式返回值為預設值select
: 布林值 指定 query 的預設 projectionsvalidate
: 函式 adds a validator function for this propertyget
: 函式 使用Object.defineProperty()
定義自定義 getterset
: 函式 使用Object.defineProperty()
定義自定義 setteralias
: 字串 僅mongoose >= 4.10.0。 為該欄位路徑定義虛擬值 gets/sets
var numberSchema = new Schema({ integerOnly: { type: Number, get: v => Math.round(v), set: v => Math.round(v), alias: 'i' } }); var Number = mongoose.model('Number', numberSchema); var doc = new Number(); doc.integerOnly = 2.001; doc.integerOnly; // 2 doc.i; // 2 doc.i = 3.001; doc.integerOnly; // 3 doc.i; // 3 複製程式碼
索引相關
var schema2 = new Schema({
test: {
type: String,
index: true,
unique: true // Unique index. If you specify `unique: true`
// specifying `index: true` is optional if you do `unique: true`
}
});
複製程式碼
String
lowercase
: 布林值 是否在儲存前對此值呼叫.toLowerCase()
uppercase
: 布林值 是否在儲存前對此值呼叫.toUpperCase()
trim
: 布林值 是否在儲存前對此值呼叫.trim()
match
: 正規表示式 建立驗證器檢查這個值是否匹配給定正規表示式enum
: 陣列 建立驗證器檢查這個值是否包含於給定陣列
Number
Date
min
: Datemax
: Date
驗證validation
- 驗證定義於 SchemaType
- 驗證是一個中介軟體。它預設作為 pre('save')` 鉤子註冊在 schema 上
- 你可以使用
doc.validate(callback)
或doc.validateSync()
手動驗證 - 驗證器不對未定義的值進行驗證,唯一例外是
required
驗證器 - 驗證是非同步遞迴的。當你呼叫 Model#save,子文件驗證也會執行,出錯的話 Model#save 回撥會接收錯誤
- 驗證是可定製的
var schema = new Schema({
name: {
type: String,
required: true
}
});
var Cat = db.model('Cat', schema);
// This cat has no name :(
var cat = new Cat();
cat.save(function(error) {
assert.equal(error.errors['name'].message,
'Path `name` is required.');
error = cat.validateSync();
assert.equal(error.errors['name'].message,
'Path `name` is required.');
});
複製程式碼
內建validators
Mongoose有一些內建驗證器
- 所有 SchemaTypes 都有內建 required 驗證器。required 驗證器使用
checkRequired()
函式 判定這個值是否滿足 required 驗證器 - Numbers 有 min 和 max 驗證器.
- Strings 有 enum、 match、 maxlength 和 minlength 驗證器
自定義驗證器
自定義驗證器通過傳入一個檢驗函式來定義
var userSchema = new Schema({
phone: {
type: String,
validate: {
validator: function(v) {
return /\d{3}-\d{3}-\d{4}/.test(v);
},
message: '{VALUE} is not a valid phone number!'
},
required: [true, 'User phone number required']
}
});
var User = db.model('user', userSchema);
var user = new User();
var error;
user.phone = '555.0123';
error = user.validateSync();
assert.equal(error.errors['phone'].message,
'555.0123 is not a valid phone number!');
user.phone = '';
error = user.validateSync();
assert.equal(error.errors['phone'].message,
'User phone number required');
user.phone = '201-555-0123';
// Validation succeeds! Phone number is defined
// and fits `DDD-DDD-DDDD`
error = user.validateSync();
assert.equal(error, null);
複製程式碼
非同步自定義驗證器
自定義檢驗器可以是非同步的。如果檢驗函式 返回 promise (像 async
函式), mongoose 將會等待該 promise 完成。 如果你更喜歡使用回撥函式,設定 isAsync
選項, mongoose 會將回撥函式作為驗證函式的第二個引數。
var userSchema = new Schema({
name: {
type: String,
// You can also make a validator async by returning a promise. If you
// return a promise, do **not** specify the `isAsync` option.
validate: function(v) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(false);
}, 5);
});
}
},
phone: {
type: String,
validate: {
isAsync: true,
validator: function(v, cb) {
setTimeout(function() {
var phoneRegex = /\d{3}-\d{3}-\d{4}/;
var msg = v + ' is not a valid phone number!';
// 第一個引數是布林值,代表驗證結果
// 第二個引數是報錯資訊
cb(phoneRegex.test(v), msg);
}, 5);
},
// 預設報錯資訊會被 `cb()` 第二個引數覆蓋
message: 'Default error message'
},
required: [true, 'User phone number required']
}
});
var User = db.model('User', userSchema);
var user = new User();
var error;
user.phone = '555.0123';
user.name = 'test';
user.validate(function(error) {
assert.ok(error);
assert.equal(error.errors['phone'].message,
'555.0123 is not a valid phone number!');
assert.equal(error.errors['name'].message,
'Validator failed for path `name` with value `test`');
});
複製程式碼
中介軟體middleware
中介軟體(pre和post鉤子)是在非同步函式執行時函式傳入的控制函式,mongoose中所有的中介軟體都支援pre和post鉤子。
pre鉤子
pre鉤子分為序列和並行兩種,序列就是中介軟體一個接一個的執行,也就是上一個中介軟體呼叫next函式的時候,下一個執行。
var schema = new Schema(..);
schema.pre('save', function(next) {
// do stuff
next();
});
複製程式碼
並行中介軟體提供細粒度流控制
var schema = new Schema(..);
// 只有第二個引數為‘true'時才表示並行執行
schema.pre('save', true, function(next, done) {
// calling next kicks off the next middleware in parallel
next();
setTimeout(done, 100);
});
複製程式碼
post中介軟體
post中介軟體在方法執行後呼叫
schema.post('init', function(doc) {
console.log('%s has been initialized from the db', doc._id);
});
schema.post('validate', function(doc) {
console.log('%s has been validated (but not saved yet)', doc._id);
});
schema.post('save', function(doc) {
console.log('%s has been saved', doc._id);
});
schema.post('remove', function(doc) {
console.log('%s has been removed', doc._id);
});
複製程式碼
如果在回撥函式傳入兩個引數,mongoose會認為第二個引數是next函式,我們可以通過next觸發下一個中介軟體
// Takes 2 parameters: this is an asynchronous post hook
schema.post('save', function(doc, next) {
setTimeout(function() {
console.log('post1');
// Kick off the second post hook
next();
}, 10);
});
// Will not execute until the first middleware calls `next()`
schema.post('save', function(doc, next) {
console.log('post2');
next();
});
複製程式碼
連線Connections
我們可用mongoose.connect()連線,最簡單的如mongoose.connect('mongodb://localhost/myapp');
,當然我們也可以在uri中指定多個引數mongoose.connect('mongodb://username:password@host:port/database?options...');
我們也可以用mongoose.createConnection()來連線,它返回一個新的連線,connection物件後面用於建立和檢索models。
const conn = mongoose.createConnection('mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]', options);
複製程式碼
操作快取
意思就是我們不必等待連線建立成功就可以使用models,mongoose會先快取model操作
var MyModel = mongoose.model('Test', new Schema({ name: String }));
// 連線成功前操作會被掛起
MyModel.findOne(function(error, result) { /* ... */ });
setTimeout(function() {
mongoose.connect('mongodb://localhost/myapp');
}, 60000);
複製程式碼
如果要禁用快取,可修改bufferCommands配置,也可以全域性禁用bufferCommands
mongoose.set('bufferCommands', false);
複製程式碼
選項
connect方法可以接受options引數,具體有哪些引數可以參考官方文件
mongoose.connect(uri,options)
複製程式碼
const options = {
useMongoClient: true,
autoIndex: false, // Don't build indexes
reconnectTries: Number.MAX_VALUE, // Never stop trying to reconnect
reconnectInterval: 500, // Reconnect every 500ms
poolSize: 10, // Maintain up to 10 socket connections
// If not connected, return errors immediately rather than waiting for reconnect
bufferMaxEntries: 0
};
mongoose.connect(uri, options);
複製程式碼
回撥
connect函式可以接受回撥函式或者返回一個promise
mongoose.connect(uri,options,function(error){
})
mongoose.connect(uri,options).then(
() => { /** ready to use. The `mongoose.connect()` promise resolves to undefined. */ },
err => { /** handle initial connection error */ }
)
複製程式碼
注意
這裡有個連線池的概念,無論是使用 mongoose.connect
或是 mongoose.createConnection
建立的連線, 都被納入預設最大為 5 的連線池,可以通過 poolSize 選項調整:
// With object options
mongoose.createConnection(uri, { poolSize: 4 });
const uri = 'mongodb://localhost/test?poolSize=4';
mongoose.createConnection(uri);
複製程式碼
子文件Subdocument
子文件指的是巢狀在另一個文件中的文件,mongoose文件有兩種不同的概念:子文件陣列和單個巢狀子文件
var childSchema = new Schema({ name: 'string' });
var parentSchema = new Schema({
// Array of subdocuments
children: [childSchema],
// Single nested subdocuments. Caveat: single nested subdocs only work
// in mongoose >= 4.2.0
child: childSchema
});
複製程式碼
子文件和普通文件類似,主要不同的是子文件不能單獨儲存,它們會在它們的頂級文件儲存時儲存
var Parent = mongoose.model('Parent', parentSchema);
var parent = new Parent({ children: [{ name: 'Matt' }, { name: 'Sarah' }] })
parent.children[0].name = 'Matthew';
// `parent.children[0].save()` 無操作,雖然他觸發了中介軟體
// 但是**沒有**儲存文件。你需要 save 他的父文件
parent.save(callback);
複製程式碼
常用的操作:
-
Model.find Model.find(query,fields,options,callback)//fields和options都是可選引數 簡單查詢 Model.find({'csser.com':5},function(err,docs){docs是查詢的結果陣列}); 只查詢指定鍵的結果 Model.find({},['first','last'],function(err,docs){//docs此時只包含文件的部分鍵值})
-
Model.findOne 它只返回單個文件 Model.findOne({age:5},function(err,doc){//doc是單個文件});
-
Model.findById 與findOne相同,但它接收文件的_id作為引數,返回單個文件。_id可以是字串或者ObjectID物件 Model.findById(obj._id,function(err,doc){//doc是單個文件})
-
Model.count 返回符合條件的文件數 Model.count(conditions,callback)
-
Model.remove 刪除符合條件的文件 Model.remove(conditions,callback)
-
Model.distinct 查詢符合條件的文件並返回根據鍵分組的結果
Model.distinct(field,conditions,callback)
-
Model.where 當查詢比較複雜的時候就用where Model.where('age').gte(25)
where('tags').in(['movie','music','art']) .select('name','age','tags') .skip(20) .limit(10) .asc('age') .slaveOk() .hint({age:1,name:1}) .run(callback)
-
Model.$where
有時我們需要在MongoDB中使用JavaScript表示式進行查詢,這時可以使用find({ where:javascript})方式,where是一種快捷方式,並支援鏈式呼叫查詢 Model.$where('this.firstname===this.lastname').exec(callback)
-
Model.update
使用update子句更新指定條件的文件,更新資料在傳送到資料庫伺服器之前會改變模型的型別,注意update返回的是被修改的文件數量 var conditions={name:'borne'} ,update={$inc:{visits:1}} ,options={multi:true} Model.update(conditions,update,options,callback)
注意:為了向後相容,所有頂級更新鍵如果不是原子操作命名的,會被統一按$set操作處理,例如:
var queryb={name:'borne'};
Model.update(query,{name:'jason borne'},options,callback)
上面會被這樣傳送到資料庫伺服器的
Model.update(query,{$set:{name:'jason borne'}},options,callback)
-
查詢API 如果不提供回撥函式,所有這些方法都返回Query物件,它們都可以被再次修改,直到呼叫exec方法 var query=Model.find({});
query.where('field',5);
query.limit(5);
query.skip(100);