0 简介
Nodejs有针对MongoDB的数据库驱动: mongodb。 npm install mongodb即可安装
不过直接使用mongodb模块虽然强大而灵活,但有些繁琐。 直接使用mongoose!
mongoose建立在mongodb之上,提供了Schema、 Model 和 Document对象
我们可以用Schema对象定义文档的结构(类似表结构),可以定义字段和类型、唯一性、索引和验证。
注:不需要为每个操作都connect一次database!
一 安装
npm install mongoose --save
然后在你的项目中引入
var mongoose=require('mongoose');
二 使用mongoose
建议先看新手入门:
http://www.nodeclass.com/api/mongoose.html#quick_start
使用mongoose可以新建数据库、新建集合、对集合内的文档进行CRUD操作,在写代码时,可以对照着mongo shell验证结果是否符合预期。
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/accounts');
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function() {
console.log('mongoose opened!');
var userSchema = new mongoose.Schema({
name:{type: String, unique: true},
password:String
},
{collection: "accounts"}
);
var User = mongoose.model('accounts', userSchema);
User.findOne({name:"Jack"}, function(err, doc){
if(err) console.log(err);
else console.log(doc.name + ", password - " + doc.password);
});
var lisi = new User({name:"Jxx", password:"123"});
lisi.save(function(err, doc){
if(err)console.log(err);
else console.log(doc.name + ' saved');
});
});
要使用mongoose,先require,然后使用connect方法连接数据库。
mongoose.connect('mongodb://localhost/myapp');
这是我们connect到数据库,默认端口27017的最简单方式。
mongoose.connect('mongodb://username:password@host:port/database?options...');
connect(url,optios,[callback])
//指定特定参数。
mongoose的connection对象定义了一些事件,比如connected、open、close、error等,我们可以监听这些事件。
需要注意的是:定义Schema时指定的Collection名字与mongoose.model第一个参数保持一致,这就是你的集合名称。
拿到了Model对象,就可以执行增删改查等操作了。Model对象有find()、findOne()、update()、remove()等方法。
- 这些方法都有一个可选的callback,当你提供这些callback时,执行的结果会通过这个callback返回给你。
- 如果你不提供,这些方法会返回一个Query对象,你可以再通过Query组装新的选项,然后调用Query的exec(callback)来提交查询。
三 mongoose 概念及说明
Schema
我们可以用schema对象定义文档的结构,定义字段和类型,唯一性 索引和验证。
Model对象表示集合中的所有文档。Document对象表示集合中单个文档的表示。
mongoose还有Query和Aggregate对象。
每一个schema都映射到一个mongoDB集合,定义了这个集合中文档的结构,属性
下面是一个Schema的例子
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
}
});
SchemaTypes
类型:
- String
- Number
- Date
- Buffer
- Boolean
- Mixed
- Objectid
- Array
Schema.Type 是由Mongoose内地的一些数据类型,基本数据库类型都在其中,他也内置了一些Mongoose特有的类型。 也可以自定义。只有满足SchemaType的类型才能在Schema内定义。
不过Schema不仅定义了你的文档的结构以及属性的映射,还有文档实例方法,静态模型方法,索引,文档的生命周期。
Schema.Types.Mixed
是Mongoose定义的一个混合类型
用这个括号定义的就是混合类型了~
var Any=new Schema({any:{}});
var Any=new Schema({any:Schema.Types.Mixed});
两种是等价的。 你可以改变它的值称为任意的值。
但是这就导致了它不会自动检测这个值的改变。
告诉Mongoose Mixed 类型的值改变了:
person.anything={x:[3,4,{y:"changed"}]};
person.markModified('anything');
person.save();
ObjectIds
主键,一种特殊且非常重要的类型,每个Schema都会默认配置这个属性,属性名为_id 。
var mongoose = require('mongoose');
var ObjectId = mongoose.Schema.Types.ObjectId;
var StudentSchema = new Schema({}); //默认会有_id:ObjectId
var TeacherSchema = new Schema({id:ObjectId});//只有id:ObjectId
该类型的值系统自己生成,从某种意义上几乎不会重复,生成过程比较复杂
Array
在数组中可以存入不同的值.
数组类型提供了创建 SchemaTypes的数组 或者
Sub-Documents
var ToySchema=new Schema({name:String});
var ToyBox=new Schema({
toys:[ToySchema],
buffers:[Buffer],
string:[String],
numbers:[Number]
});
上面这个例子说明了, 你可以定义Number类型数组,String类型数组等等 甚至是 Schema这种mix类型数组!
Schema扩展
扩展实例方法
我们的Schema要为后面的Model和Entity不单单是提供属性,还需要提供这些实例可以用的公共方法(一张蓝图)
这时用 method!
userSchema.methods={
comparePassword:function(_password,cb){
bcrypt.compare(_password,this.password,function(err,isMatch){
if(err) return cb(err);
cb(null,isMatch);
})
}
}
使用: (一般是在控制器层使用,model层的还不是一个实例对象)
user.comparePassword(password,function(err,isMatch){
if(err){
console.log(err)
return res.json({msg:'error',result:3});
}
if(isMatch){
console.log('Password is matched!');
req.session.user=user;
return res.json({msg:'username match!',result:0});
}else{
console.log('Password is not match');
return res.json({msg:'password is not matched',result:2});
//return res.redirect('/user/login');
}
})
静态方法
静态方法在Model层就能使用
有几个内置的静态方法: 可以用于查询
find findById findOne where
userSchema.statics={
//get alll all data
fetch:function(cb){
return this
.find({})
.sort('meta.updateAt')//sort by update time
.exec(cb)
},
findById:function(id,cb){
return this
.findOne({_id:id}) //one item
.exec(cb)
}
}
使用:
var User=mongoose.model('User',userSchema);
User.findOne({name:"xxx"},function(err,user){
if(err){}
});
Model
model是一个类我们建造documents。 在schema中定义的方法method会被写进Model的原型。
并在每个documents实例中得到使用。
Model()是一个构造器
var kitten=mongoose.model('Kitten',kittySchema);
我们上面的代码把一个Schema编译成一个model,对应在数据库中的集合名称为’Kitten’
Model()是一个构造器,Model的实例就是documents Documents有很多自己的内置实例方法。
什么是Model?
Model模型,是经过Schema构造来的,除了Schema定义的数据库骨架以外,还具有数据库的行为模型,相当于管理数据库属性、行为的类!
创建Model
var TankModel=mongoose.model(‘Tank’,TankSchema); //通过 TankSchema创建模型
这个TankModel都是一个模型(类) 了。
你可以直接调用它的方法如:
TankModel.create(); TankModel.xxx()
你也可以使用这个模型(类)创建Entity实例:
但实例只能用自己的方法(原型上的)
var tankEntity=new TankModel(‘someother’,’size:big’);
tankEntity.save();
API介绍:
mongoose.model(name,[schema],[collection],[skipInit])
参数:
name 模型名称
[schema]
[collection] name (可选,默认是model name)
所以说你可以传递这个model name作为集合名称,但如果你不喜欢,你就设置collection名称。
var M = mongoose.model('Actor', schema, 'actor')
Model的实例就是documents对象, Documents有很多自己的内置实例方法,我们可以创建以及保存到数据库:
var Tank = mongoose.model('Tank', yourSchema);
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!
})
这里创建了一个model实例,以及保存到
Instance methods
find() 方法 和 save() 方法都是mongoose内置好的方法
find() findById() findOne()
removing
Tank.remove({size:"large'},function(err){
//...
});
Documents
mongoose 的文档,表示 一对一的映射,和MongoDB中的数据库。
每一个document都是一个model的实例
Document可以等同于Entity ,具有属性和操作性。
Document的CRUD都必须经过严格验证。
Update更新
传统方式:
PersonModel.findById(id,function(err,person){
person.name = 'MDragon';
person.save(function(err){});
});
这里,利用Model模型查询到了person对象,该对象属于Entity,可以有save操作。
使用save 来覆盖旧的值。
PersonModel.findById(id,function(err,person){
person.name = 'MDragon';
var _id = person._id; //需要取出主键_id
delete person._id; //再将其删除
PersonModel.update({_id:_id},person,function(err){
});
//此时才能用Model操作,否则报错
});
update第一个参数是查询条件,第二个参数是更新的对象,但不能更新主键,这就是为什么要删除主键的原因!这里我们是先查找有没有再更新!
还有一种使用$set
更新同时而且要返回更新后的对象!
Tank.findByIdAndUpdate(id,{$set:{size:'large'}},function(err,tank){
if(err) return handleError(err);
res.send(tank);
});
还有类似的方法 findByIdAndRemove
这种静态方法最多对一个文档进行一次改变。然后返回它。
注意: findByIdAndUpdate/Remove do not execute any hooks or validation before making change in the database. 改变数据库中的数据时不会触发任何钩子或验证
新增
如果是Entity(实例),使用save方法,如果是model使用create方法.
//此处的user 是一个Entity(document)
user=new User({
username:username,
password:req.body.password,
email:req.body.email
});
user.save(function(err,user){
if(err){
console.log(err);
return res.json({msg:'Error',result:1});
}
return res.json({msg:'Success',result:0});
});
而对于Model
则为:
//使用Model来增加一条数据
var MDragon = {name:'MDragon'};
PersonModel.create(MDragon,callback);
区别就是: 如果使用Model新增,传入的对象可以是Array 或者 对象 Object ,而不能是Model创建的实例。
Create是一种快捷创建文档的方式
Shortcut for creating a new Document that is automatically saved to the db if valid.
删除
和新增一样,删除也有两种方式
但是实例Entity 和 Model都是用remove方法
查询Query
查询的方式通常有2种查询方式,一种是直接查询,一种是链式查询
其实也可以不这么区分。
Document文档可以被 几个static helper方法给查询出来。注意是Model的静态方法。
1) 直接查询
在查询时带有回调函数的称为直接查询,查询的条件通常是通过API设定:
var Person = mongoose.model('Person', yourSchema);
// find each person with a last name matching 'Ghost', selecting the `name` and `occupation` fields
Person.findOne({ 'name.last': 'Ghost' }, 'name occupation', function (err, person) {
if (err) return handleError(err);
console.log('%s %s is a %s.', person.name.first, person.name.last, person.occupation)
})
这种查询会立即执行,并把结果传递给我们的回调函数。所有的回调在Mongoose里面 使用这种模式:
callback(error,result) 其实跟nodejs的形式很像。
- 如果错误发生,result 为 null ,error会包含一个error document。
- 如果成功 error会为 null ,result会populated with the results of the query
2)链式查询
查询的时候,不带回调,查询条件也是通过API函数指定。
var query = PersonModel.findOne({'name.last':'dragon'});
query.select('some select');
query.exec(function(err,pserson){
//如果err==null,则person就能取到数据
});
可以看出来,查询方法findOne会返回一个Query实例,然后我们又可以针对这个查询的结果再筛选或执行一些操作
Person
.find({ occupation: /host/ })
.where('name.last').equals('Ghost')
.where('age').gt(17).lt(66)
.where('likes').in(['vaporizing', 'talking'])
.limit(10)
.sort('-occupation')
.select('name occupation')
.exec(callback);
Population
没有joins 在MongoDB ,但是我们可能希望使用在文档里面使用引用,引用其他集合中的文档。
Population是一个自动替换指定的路径在文档中字段为其他集合中文档的一个过程。
不管是文档,多个文档,普通object,多个对象都可以。
var mongoose = require('mongoose')
, Schema = mongoose.Schema
var personSchema = Schema({
_id : Number,
name : String,
age : Number,
stories : [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});
var storySchema = Schema({
_creator : { type: Number, ref: 'Person' },
title : String,
fans : [{ type: Number, ref: 'Person' }]
});
var Story = mongoose.model('Story', storySchema);
var Person = mongoose.model('Person', personSchema);
这里是一个例子,
我们创建了两个model, person model有它的stores字段,这个是设置成一个Objectid数组的。
ref是告诉Moogoose使用哪个model 在Population的过程中,在这例子是Story model。
所有的ids我们存储在这里必须是document_ids 来自story model。
我们同样声明了story 的 _creator字段作为一个Number ,使用相同的类型和 Person中的 _id.
match 两个模型的类型 非常重要!
Saving refs
存储引用。 跟你存储其他属性是一样的!
比如对于上面这个 person 和 story的Schema来说
var aron=new Person({_id:0,name:'Aaron',age:100});
//创建了一个实例对象 of Person
然后可以通过save方法存储到数据库中:
aron.save(function(err){
if(err) return handleError(err);
var story1=new Story({
title:"Once upono a time",
_creator:aron._id //直接把_id赋值给这个地方,尽管是来自person的字段!
})
})
Field Selection
如果只想要返回一些特定的字段,而不是整个文档。
那么可以使用populate方法来筛选如:
Story.findOne({title:'xxx'})
.populate('_creator','name') //仅仅返回Person的name
.exec(function(err,story){
console.log(story._creator.name); //aron
console.log(story._creator.age); //null
})
Validation
数据存储是需要验证的,不是什么数据都能往数据库里丢或者显示到客户端,有以下这几个规则:KEEP IN MIND
- 验证始终定义在SchemaType中
- 验证是一个内部的中间件
- 验证是在一个Document被保存时默认启动的,除非你关闭验证
- 验证是异步递归的,当你调用Model#save, sub-document也会被执行。 你的子文档验证失败,Document也无法保存!如果错误发生了,你的Model#save调用会接收它。
- 验证支持完全的定制
验证器
Mongoose有几个内置的验证器:
- 所有的SchemaType都有内置的 required 验证器
- Number 类型有 min max 验证器
- Strings有 enum和 match验证器
required表示enable or disable这个验证器
var PersonSchema = new Schema({
name:{
type:'String',
required:true //姓名非空
},
age:{
type:'Nunmer',
min:18, //年龄最小18
max:120 //年龄最大120
},
city:{
type:'String',
enum:['北京','上海'] //只能是北京、上海人
},
other:{
type:'String',
validate:[validator,err] //validator是一个验证函数,err是验证失败的错误信息
}
});
验证失败
如果验证,则返回err信息,err是一个对象该对象属性是:
//Toy是一个model
var toy=new Toy({color:'green'});
toy.save(function (err) {
// err is our ValidationError object
// err.errors.color is a ValidatorError object
console.log(err.errors.color.message) // prints 'Validator "Invalid color" failed for path color with value `grease`'
console.log(String(err.errors.color)) // prints 'Validator "Invalid color" failed for path color with value `grease`'
console.log(err.errors.color.type) // prints "Invalid color"
console.log(err.errors.color.path) // prints "color"
console.log(err.errors.color.value) // prints "grease"
console.log(err.name) // prints "ValidationError"
console.log(err.message) // prints "Validation failed"
});
Middleware中间件
什么是中间件?
中间件是函数被传递到控制流中的,当执行document的init validate save 和 remove方法时。 中间件会在document level上执行,而不是model level!
pre
var schema=new Schema(...);
schame.pre('save',function(){
//do stuff
next();
});
并行
var schema = new Schema(..);
schema.pre('save', true, function (next, done) {
// calling next kicks off the next middleware in parallel
next();
doAsync(done);
});
post
POST中间件,是在hooked方法后面执行的,以及在所有pre中间件完成之后。 post中间件不直接指定接收的flow contrl
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);
})