Mongoose裏面最重要的三個概念是Schemas,Models和Documents,另外還有驗證,中間件等,前面三個相當重要,這裏像填充Populate
和鑑別器Discriminators
的相關知識就不介紹了,平時基本上不太能用到,具體的能夠查閱官方文檔。javascript
Mongoose的一切始於Schema
,Schema
定義collection
裏的文檔的構成,models
是從Schema
編譯來的構造函數,它們的實例就表明着能夠從數據庫保存和讀取的documents
,從數據庫建立和讀取document
的操做都是經過model
進行的,而保存時咱們是在model
實例上進行的。css
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!
})
複製代碼
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
});
複製代碼
也能夠添加靜態方法html
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有不少的配置選項,能夠在構造時或者直接setjava
new Schema({..}, options);
// or
var schema = new Schema({..});
schema.set(option, value);
複製代碼
查詢文檔能夠用model的find,findById,findOne和where這些靜態方法正則表達式
remove方法mongodb
model的update方法能夠修改數據庫中的文檔,不過不會把文檔返回數據庫
findOneAndUpdate方法用來更新單獨一條文檔而且返回給應用層api
檢索方法較多,暫不闡述數組
更新的方法也比較多promise
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);
});
複製代碼
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:
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/setsvar 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`
}
});
複製代碼
lowercase
: 布爾值 是否在保存前對此值調用 .toLowerCase()
uppercase
: 布爾值 是否在保存前對此值調用 .toUpperCase()
trim
: 布爾值 是否在保存前對此值調用 .trim()
match
: 正則表達式 建立驗證器檢查這個值是否匹配給定正則表達式enum
: 數組 建立驗證器檢查這個值是否包含於給定數組min
: Datemax
: Datedoc.validate(callback)
或 doc.validateSync()
手動驗證required
驗證器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.');
});
複製代碼
Mongoose有一些內建驗證器
checkRequired()
函數 斷定這個值是否知足 required 驗證器自定義驗證器經過傳入一個檢驗函數來定義
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`');
});
複製代碼
中間件(pre和post鉤子)是在異步函數執行時函數傳入的控制函數,mongoose中全部的中間件都支持pre和post鉤子。
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中間件在方法執行後調用
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();
});
複製代碼
咱們可用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);
複製代碼
子文檔指的是嵌套在另外一個文檔中的文檔,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);