Schema
: 一种以文件形式存储的数据库模型骨架,不具有数据库的操做能力前端
Model
: 由Schema
发布生成的模型,具备抽象属性和行为的数据库操做对node
Entity
: 由Model
建立的实体,他的操做也会影响数据库mongodb
注意:数据库
1.本学习文档采用严格命名方式来区别不一样对象,例如:编程
var PersonSchema; //Person的文本属性 var PersonModel; //Person的数据库模型 var PersonEntity; //Person实体
2.Schema
、Model
、Entity
的关系请牢记,Schema
生成Model
,Model
创造Entity
,Model
和Entity
均可对数据库操做形成影响,但Model
比Entity
更具操做性。数组
2.在项目只可以建立一个数据库链接,以下:前端工程师
var mongoose = require('mongoose'); //引用mongoose模块 var db = mongoose.createConnection('localhost','test'); //建立一个数据库链接
3.打开本机localhost
的test
数据库时,咱们能够监测是否有异常app
db.on('error',console.error.bind(console,'链接错误:')); db.once('open',function(){ //一次打开记录 });
注意:异步
成功开启数据库后,就能够执行数据库相应操做,假设如下代码都在回调中处理
4.定义一个Schema
var PersonSchema = new mongoose.Schema({ name:String //定义一个属性name,类型为String });
5.将该Schema
发布为Model
var PersonModel = db.model('Person',PersonSchema); //若是该Model已经发布,则能够直接经过名字索引到,以下: //var PersonModel = db.model('Person'); //若是没有发布,上一段代码将会异常
6.用Model
建立Entity
var personEntity = new PersonModel({name:'Krouky'}); //打印这个实体的名字看看 console.log(personEntity.name); //Krouky
7.咱们甚至能够为此Schema
建立方法
//为Schema模型追加speak方法 PersonSchema.methos.speak = function(){ console.log('个人名字叫'+this.name); } var PersonModel = db.model('Person',PersonSchema); var personEntity = new PersonModel({name:'Krouky'}); personEntity.speak();//个人名字叫Krouky
8.Entity
是具备具体的数据库操做CRUD
的
personEntity.save(); //执行完成后,数据库就有该数据了
9.若是要执行查询,须要依赖Model
,固然Entity
也是能够作到的
PersonModel.find(function(err,persons){ //查询到的全部person });
注意:
1. 具体的如何配置Schema
、Model
以及Model
和Entity
的相关操做,咱们会在后面进行
2. Model
和Entity
都有能影响数据库的操做,但仍有区别,后面咱们也会作解释
若是您还不清楚Mongoose
是如何工做的,请参看第一章快速通道快速浏览他的用法吧
Schema
仅仅只是一断代码,他书写完成后程序依然没法使用,更没法通往数据库端var BlogSchema = new Schema({ title:String, author:String //new Schema()中传入一个JSON对象,该对象形如 xxx:yyyy , /xxx是一个字符串,定义了属性,yyy是一个Schema.Type,定义了属性类型 });
Schema.Type
是由Mongoose
内定的一些数据类型,基本数据类型都在其中,他也内置了一些Mongoose
特有的Schema.Type
。固然,你也能够自定义Schema.Type
,只有知足Schema.Type
的类型才能定义在Schema
内。
NodeJS
中的基本数据类型都属于Schema.Type
,另外Mongoose
还定义了本身的类型
//举例: var ExampleSchema = new Schema({ name:String, binary:Buffer, living:Boolean, updated:Date, age:Number, mixed:Schema.Types.Mixed, //该混合类型等同于nested _id:Schema.Types.ObjectId, //主键 _fk:Schema.Types.ObjectId, //外键 array:[], arrOfString:[String], arrOfNumber:[Number], arrOfDate:[Date], arrOfBuffer:[Buffer], arrOfBoolean:[Boolean], arrOfMixed:[Schema.Types.Mixed], arrOfObjectId:[Schema.Types.ObjectId] nested:{ stuff:String, } });
Buffer
和ArrayBuffer
是Nodejs
两种隐藏的对象,相关内容请查看NodeJS-API
Schema.Types.Mixed
是Mongoose
定义个混合类型,该混合类型若是未定义具体形式。所以,若是定义具体内容,就直接使用{}
来定义,如下两句等价
var AnySchema = new Schema({any:{}}); var AnySchema = new Schema({any:Schema.Types.Mixed});
混合类型由于没有特定约束,所以能够任意修改,一旦修改了原型,则必须调用markModified()
person.anything = {x:[3,4,{y:'change'}]} person.markModified('anything');//传入anything,表示该属性类型发生变化 person.save();
主键,一种特殊并且很是重要的类型,每一个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
在JavaScript
编程语言中并非数组,而是集合,所以里面能够存入不一样的值,如下代码等价:
var ExampleSchema1 = new Schema({array:[]}); var ExampleSchema2 = new Schema({array:Array}); var ExampleSchema3 = new Schema({array:[Schema.Types.Mixed]}); var ExampleSchema4 = new Schema({array:[{}]});
Schema
不只定义了文档结构
和使用性能
,还能够有扩展插件
、实例方法
、静态方法
、复合索引
、文档生命周期钩子
Schema
能够定义插件,而且插件具备良好的可拔插性,请有兴趣的读者继续日后阅读或者查阅官方资料。
有的时候,咱们创造的Schema
不只要为后面的Model
和Entity
提供公共的属性,还要提供公共的方法。
下面例子比快速通道的例子更加高级,能够进行高级扩展:
var PersonSchema = new Schema({name:String,type:String}); //查询相似数据 PersonSchema.methods.findSimilarTypes = function(cb){ return this.model('Person').find({type:this.type},cb); }
使用以下:
var PersonModel = mongoose.model('Person',PersonSchema); var krouky = new PersonSchema({name:'krouky',type:'前端工程师'}); krouky.findSimilarTypes(function(err,persons){ //persons中就能查询到其余前端工程师 });
静态方法在Model
层就能使用,以下:
PersonSchema.statics.findByName = function(name,cb){ this.find({name:new RegExp(name,'i'),cb}); } var PersonModel = mongoose.model('Person',PersonSchema); PersonModel.findByName('krouky',function(err,persons){ //找到全部名字叫krouky的人 });
索引或者复合索引能让搜索更加高效,默认索引就是主键索引ObjectId
,属性名为_id
, 索引会做为一个专题来说解
Schema
中若是定义了虚拟属性,那么该属性将不写入数据库,例如:
var PersonSchema = new Schema({ name:{ first:String, last:String } }); var PersonModel = mongoose.model('Person',PersonSchema); var krouky = new PersonModel({ name:{first:'krouky',last:'han'} });
若是每次想使用全名就得这样
console.log(krouky.name.first + ' ' + krouky.name.last);
显然这是很麻烦的,咱们能够定义虚拟属性
:
PersonSchema.virtual('name.full').get(function(){ return this.name.first + ' ' + this.name.last; });
那么就能用krouky.name.full
来调用全名了,反之若是知道full,也能够反解first
和last
属性
PersonSchema.virtual('name.full').set(function(name){ var split = name.split(' '); this.name.first = split[0]; this.name.last = split[1]; }); var PersonModel = mongoose.model('Person',PersonSchema); var krouky = new PersonModel({}); krouky.name.full = 'krouky han';//会被自动分解 console.log(krouky.name.first);//krouky
在使用new Schema(config)
时,咱们能够追加一个参数options
来配置Schema
的配置,形如:
var ExampleSchema = new Schema(config,options);
或者使用
var ExampleSchema = new Schema(config); ExampleSchema.set(option,value);
可供配置项有:safe
、strict
、capped
、versionKey
、autoIndex
2.5.1 safe——安全属性(默认安全)
通常可作以下配置:
new Schema({...},{safe:true});
固然咱们也能够这样
new Schema({...},{safe:{j:1,w:2,wtimeout:10000}});
j
表示作1份日志,w
表示作2个副本(尚不明确),超时时间10秒
2.5.2 strict——严格配置(默认启用)
确保Entity
的值存入数据库前会被自动验证,若是你没有充足的理由,请不要停用,例子:
var ThingSchema = new Schema({a:String}); var ThingModel = db.model('Thing',SchemaSchema); var thing = new Thing({iAmNotInTheThingSchema:true}); thing.save();//iAmNotInTheThingSchema这个属性将没法被存储
若是取消严格选项,iAmNotInTheThingSchema
将会被存入数据库
该选项也能够在构造实例时使用,例如:
var ThingModel = db.model('Thing'); var thing1 = new ThingModel(doc,true); //启用严格 var thing2 = new ThingModel(doc,false); //禁用严格
注意:
strict
也能够设置为throw
,表示出现问题将会抛出错误
2.5.3 shardKey
须要mongodb
作分布式,才会使用该属性
2.5.4 capped——上限设置
若是有数据库的批量操做,该属性能限制一次操做的量,例如:
new Schema({...},{capped:1024}); //一次操做上线1024条数据
固然该参数也但是JSON对象,包含size、max、autiIndexId属性
new Schema({...},{capped:{size:1024,max:100,autoIndexId:true}});
2.5.5 versionKey——版本锁
版本锁是Mongoose
默认配置(__v属性)的,若是你想本身定制,以下:
new Schema({...},{versionKey:'__someElse'});
此时存入数据库的版本锁就不是__v
属性,而是__someElse
,至关因而给版本锁取名字。
具体怎么存入都是由Mongoose
和MongoDB
本身决定,固然,这个属性你也能够去除
new Schema({...},{versionKey:false});
除非你知道你在作什么,而且你知道这样作的后果
2.5.6 autoIndex——自动索引
该内容将在索引章节单独讲解
Document
是与MongoDB
文档一一对应的模型,Document
可等同于Entity
,具备属性和操做性
注意:
Document
的`CRUD都必须通过严格验证的,参看2.5.2 Schema的strict严格配置
查询内容过多,专题讲解
有许多方式来更新文件,如下是经常使用的传统方式:
PersonModel.findById(id,function(err,person){ person.name = 'MDragon'; person.save(function(err){}); });
这里,利用Model
模型查询到了person
对象,该对象属于Entity
,能够有save操做,若是使用
Model`操做,需注意:
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
属性来配置,这样也不用先查询,若是更新的数据比较少,可用性仍是很好的:
PersonModel.update({_id:_id},{$set:{name:'MDragon'}},function(err){});
须要注意,Document
的CRUD
操做都是异步执行,callback
第一个参数必须是err
,而第二个参数各个方法不同,update
的callback
第二个参数是更新的数量,若是要返回更新后的对象,则要使用以下方法
Person.findByIdAndUpdate(_id,{$set:{name:'MDragon'}},function(err,person){ console.log(person.name); //MDragon });
相似的方法还有findByIdAndRemove
,如同名字,只能根据id查询并做update
/remove
操做,操做的数据仅一条
若是是Entity
,使用save
方法,若是是Model
,使用create
方法
//使用Entity来增长一条数据 var krouky = new PersonModel({name:'krouky'}); krouky.save(callback); //使用Model来增长一条数据 var MDragon = {name:'MDragon'}; PersonModel.create(MDragon,callback);
两种新增方法区别在于,若是使用Model
新增时,传入的对象只能是纯净的JSON
对象,不能是由Model
建立的实体,缘由是:由Model
建立的实体krouky
虽然打印是只有{name:'krouky'}
,可是krouky
属于Entity
,包含有Schema
属性和Model
数据库行为模型。若是是使用Model
建立的对象,传入时必定会将隐藏属性也存入数据库,虽然3.x
追加了默认严格属性,但也没必要要增长操做的报错
和新增同样,删除也有2种方式,但Entity
和Model
都使用remove
方法
如同SQL
数据库中2张表有主外关系,Mongoose
将2个Document
的嵌套叫作Sub-Docs
(子文档)
简单的说就是一个Document
嵌套另一个Document
或者Documents
:
var ChildSchema1 = new Schema({name:String}); var ChildSchema2 = new Schema({name:String}); var ParentSchema = new Schema({ children1:ChildSchema1, //嵌套Document children2:[ChildSchema2] //嵌套Documents });
Sub-Docs
享受和Documents
同样的操做,可是Sub-Docs
的操做都由父类去执行
var ParentModel = db.model('Parent',parentSchema); var parent = new ParentModel({ children2:[{name:'c1'},{name:'c2'}] }); parent.children2[0].name = 'd'; parent.save(callback);
parent
在执行保存时,因为包含children2
,他是一个数据库模型对象,所以会先保存chilren2[0]
和chilren2[1]
。
若是子文档在更新时出现错误,将直接报在父类文档中,能够这样处理:
ChildrenSchema.pre('save',function(next){ if('x' === this.name) return next(new Error('#err:not-x')); next(); }); var parent = new ParentModel({children1:{name:'not-x'}}); parent.save(function(err){ console.log(err.message); //#err:not-x });
若是children
是parent
的子文档,能够经过以下方法查询到children
var child = parent.children.id(id);
子文档是父文档的一个属性,所以按照属性的操做便可,不一样的是在新增父类的时候,子文档是会被先加入进去的。
若是ChildrenSchema
是临时的一个子文档,不做为数据库映射集合,能够这样:
var ParentSchema = new Schema({ children:{ name:String } }); //其实就是匿名混合模式
Model
模型,是通过Schema
构造来的,除了Schema
定义的数据库骨架之外,还具备数据库行为模型,他至关于管理数据库属性、行为的类
你必须经过Schema
来建立,以下:
//先建立Schema var TankSchema = new Schema({ name:'String', size:'String' }); //经过Schema建立Model var TankModel = mongoose.model('Tank',TankSchema);
该模型就能直接拿来操做,具体查看API
,例如:
var tank = {'something',size:'small'}; TankModel.create(tank);
注意:
你可使用Model
来建立Entity
,Entity
实体是一个特有Model
具体对象,可是他并不具有Model
的方法,只能用本身的方法。
//经过Model建立Entity var tankEntity = new TankModel('someother','size:big'); tankEntity.save();
查询是数据库中运用最多也是最麻烦的地方,这里对Query
解读的并不完善,仅仅是本身的一点领悟而已。
一般有2种查询方式,一种是直接查询
,一种是链式查询
(2种查询都是本身命名的)
6.1.1 直接查询
在查询时带有回调函数的,称之为直接查询,查询的条件每每经过API
来设定,例如:
PersonModel.findOne({'name.last':'dragon'},'some select',function(err,person){ //若是err==null,则person就能取到数据 });
具体的查询参数,请查询API
6.1.2 链式查询
在查询时候,不带回调,而查询条件经过API
函数来制定,例如:
var query = PersonModel.findOne({'name.last':'dragon'}); query.select('some select'); query.exec(function(err,pserson){ //若是err==null,则person就能取到数据 });
这种方式相对直接查询,分的比较明细,若是不带callback
,则返回query
,query
没有执行的预编译查询语句,该query
对象执行的方法都将返回本身,只有在执行exec
方法时才执行查询,并且必须有回调。
由于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);
数据的存储是须要验证的,不是什么数据都能往数据库里丢或者显示到客户端的,数据的验证须要记住如下规则:
SchemaType
中Document
被保存时默认启用的,除非你关闭验证SubDoc
验证失败,Document
也将没法保存ValidationError
这个对象能够访问required
非空验证min
/max
范围验证(边值验证)enum
/match
枚举验证/匹配验证validate
自定义验证规则如下是综合案例:
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
是一个对象该对象属性以下
err.errors //错误集合(对象) err.errors.color //错误属性(Schema的color属性) err.errors.color.message //错误属性信息 err.errors.path //错误属性路径 err.errors.type //错误类型 err.name //错误名称 err.message //错误消息
一旦验证失败,Model
和Entity
都将具备和err
同样的errors
属性
中间件是一种控制函数,相似插件,能控制流程中的init
、validate、
save、
remove`方法
中间件分为两类
8.2.1 Serial串行
串行使用pre
方法,执行下一个方法使用next
调用
var schema = new Schema(...); schema.pre('save',function(next){ //作点什么 next(); });
8.2.2 Parallel并行
并行提供更细粒度的操做
var schema = new Schema(...); schema.pre('save',function(next,done){ //下一个要执行的中间件并行执行 next(); doAsync(done); });
一旦定义了中间件,就会在所有中间件执行完后执行其余操做,使用中间件能够雾化模型,避免异步操做的层层迭代嵌套
doc
例如,能够用来作自定义错误处理
schema.pre('save',function(next){ var err = new Eerror('some err'); next(err); }); entity.save(function(err){ console.log(err.message); //some err });
原文连接:http://cnodejs.org/topic/504b4924e2b84515770103dd