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);