Mongoose入门

1、快速通道

1.1 名词解释

  • Schema : 一种以文件形式存储的数据库模型骨架,不具有数据库的操做能力前端

  • Model : 由Schema发布生成的模型,具备抽象属性和行为的数据库操做对node

  • Entity : 由Model建立的实体,他的操做也会影响数据库mongodb

注意:数据库

1.本学习文档采用严格命名方式来区别不一样对象,例如:编程

var PersonSchema;   //Person的文本属性
    var PersonModel;    //Person的数据库模型
    var PersonEntity;   //Person实体

2.SchemaModelEntity的关系请牢记,Schema生成ModelModel创造EntityModelEntity均可对数据库操做形成影响,但ModelEntity更具操做性。数组

1.2 准备工做

1.首先你必须安装MongoDBNodeJS安全

2.在项目只可以建立一个数据库链接,以下:前端工程师

var mongoose = require('mongoose');    //引用mongoose模块
    var db = mongoose.createConnection('localhost','test'); //建立一个数据库链接

3.打开本机localhosttest数据库时,咱们能够监测是否有异常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. 具体的如何配置SchemaModel以及ModelEntity的相关操做,咱们会在后面进行

  2. ModelEntity都有能影响数据库的操做,但仍有区别,后面咱们也会作解释

2、新手指引

  若是您还不清楚Mongoose是如何工做的,请参看第一章快速通道快速浏览他的用法吧

1. Schema——纯洁的数据库原型

1.1 什么是Schema

  • 我理解Schema仅仅只是一断代码,他书写完成后程序依然没法使用,更没法通往数据库端
  • 他仅仅只是数据库模型在程序片断中的一种表现,或者是数据属性模型

1.2 如何定义Schema

var BlogSchema = new Schema({
      title:String,
      author:String
      //new Schema()中传入一个JSON对象,该对象形如 xxx:yyyy ,
      /xxx是一个字符串,定义了属性,yyy是一个Schema.Type,定义了属性类型
    });

1.3 什么是Schema.Type

  Schema.Type是由Mongoose内定的一些数据类型,基本数据类型都在其中,他也内置了一些Mongoose特有的Schema.Type。固然,你也能够自定义Schema.Type,只有知足Schema.Type的类型才能定义在Schema内。

1.4 Schema.Types

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

1.5 关于Buffer

  BufferArrayBufferNodejs两种隐藏的对象,相关内容请查看NodeJS-API

1.6 关于Mixed

  Schema.Types.MixedMongoose定义个混合类型,该混合类型若是未定义具体形式。所以,若是定义具体内容,就直接使用{}来定义,如下两句等价

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

1.7 关于ObjectId

  主键,一种特殊并且很是重要的类型,每一个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

  该类型的值由系统本身生成,从某种意义上几乎不会重复,生成过程比较复杂,有兴趣的朋友能够查看源码。

1.8 关于Array

  ArrayJavaScript编程语言中并非数组,而是集合,所以里面能够存入不一样的值,如下代码等价:

var ExampleSchema1 = new Schema({array:[]});
    var ExampleSchema2 = new Schema({array:Array});
    var ExampleSchema3 = new Schema({array:[Schema.Types.Mixed]});
    var ExampleSchema4 = new Schema({array:[{}]});

1.9 附言

  Schema不只定义了文档结构使用性能,还能够有扩展插件实例方法静态方法复合索引文档生命周期钩子

  Schema能够定义插件,而且插件具备良好的可拔插性,请有兴趣的读者继续日后阅读或者查阅官方资料。

2. Schema的扩展

2.1 实例方法

  有的时候,咱们创造的Schema不只要为后面的ModelEntity提供公共的属性,还要提供公共的方法。

  下面例子比快速通道的例子更加高级,能够进行高级扩展:

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中就能查询到其余前端工程师
    });

2.2 静态方法

  静态方法在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的人
  });

2.3 索引

  索引或者复合索引能让搜索更加高效,默认索引就是主键索引ObjectId,属性名为_id, 索引会做为一个专题来说解

2.4 虚拟属性

  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,也能够反解firstlast属性

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

2.5 配置项

  在使用new Schema(config)时,咱们能够追加一个参数options来配置Schema的配置,形如:

var ExampleSchema = new Schema(config,options);

  或者使用

var ExampleSchema = new Schema(config);
    ExampleSchema.set(option,value);

  可供配置项有:safestrictcappedversionKeyautoIndex

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,至关因而给版本锁取名字。

  具体怎么存入都是由MongooseMongoDB本身决定,固然,这个属性你也能够去除

new Schema({...},{versionKey:false});

  除非你知道你在作什么,而且你知道这样作的后果

2.5.6 autoIndex——自动索引

  该内容将在索引章节单独讲解

3. Documents

  Document是与MongoDB文档一一对应的模型,Document可等同于Entity,具备属性和操做性

注意:

  Document的`CRUD都必须通过严格验证的,参看2.5.2 Schema的strict严格配置

3.1 查询

  查询内容过多,专题讲解

3.2 更新

  有许多方式来更新文件,如下是经常使用的传统方式:

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

  须要注意,DocumentCRUD操做都是异步执行,callback第一个参数必须是err,而第二个参数各个方法不同,updatecallback第二个参数是更新的数量,若是要返回更新后的对象,则要使用以下方法

Person.findByIdAndUpdate(_id,{$set:{name:'MDragon'}},function(err,person){
      console.log(person.name); //MDragon
    });

  相似的方法还有findByIdAndRemove,如同名字,只能根据id查询并做update/remove操做,操做的数据仅一条

3.3 新增

  若是是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追加了默认严格属性,但也没必要要增长操做的报错

3.4 删除

  和新增同样,删除也有2种方式,但EntityModel都使用remove方法

4.Sub Docs

  如同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
    });

4.1 查询子文档

  若是childrenparent的子文档,能够经过以下方法查询到children

var child = parent.children.id(id);

4.2 新增、删除、更新

  子文档是父文档的一个属性,所以按照属性的操做便可,不一样的是在新增父类的时候,子文档是会被先加入进去的。

  若是ChildrenSchema是临时的一个子文档,不做为数据库映射集合,能够这样:

var ParentSchema = new Schema({
      children:{
        name:String
      }
    });
    //其实就是匿名混合模式

5.Model

5.1 什么是Model

  Model模型,是通过Schema构造来的,除了Schema定义的数据库骨架之外,还具备数据库行为模型,他至关于管理数据库属性、行为的类

5.2 如何建立Model

  你必须经过Schema来建立,以下:

//先建立Schema
    var TankSchema = new Schema({
      name:'String',
      size:'String' 
    });
    //经过Schema建立Model
    var TankModel = mongoose.model('Tank',TankSchema);

5.2 操做Model

  该模型就能直接拿来操做,具体查看API,例如:

var tank = {'something',size:'small'};
    TankModel.create(tank);

注意:

  你可使用Model来建立EntityEntity实体是一个特有Model具体对象,可是他并不具有Model的方法,只能用本身的方法。

//经过Model建立Entity
  var tankEntity = new TankModel('someother','size:big');
  tankEntity.save();

6.Query

  查询是数据库中运用最多也是最麻烦的地方,这里对Query解读的并不完善,仅仅是本身的一点领悟而已。

6.1 查询的方式

  一般有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,则返回queryquery没有执行的预编译查询语句,该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);

7.Validation

  数据的存储是须要验证的,不是什么数据都能往数据库里丢或者显示到客户端的,数据的验证须要记住如下规则:

  • 验证始终定义在SchemaType
  • 验证是一个内部中间件
  • 验证是在一个Document被保存时默认启用的,除非你关闭验证
  • 验证是异步递归的,若是你的SubDoc验证失败,Document也将没法保存
  • 验证并不关心错误类型,而经过ValidationError这个对象能够访问

7.1 验证器

  1. required 非空验证
  2. min/max 范围验证(边值验证)
  3. enum/match 枚举验证/匹配验证
  4. 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是验证失败的错误信息
      }
    });

7.2 验证失败

  若是验证失败,则会返回err信息,err是一个对象该对象属性以下

err.errors                //错误集合(对象)
    err.errors.color          //错误属性(Schema的color属性)
    err.errors.color.message  //错误属性信息
    err.errors.path             //错误属性路径
    err.errors.type             //错误类型
    err.name                //错误名称
    err.message                 //错误消息

  一旦验证失败,ModelEntity都将具备和err同样的errors属性

8.Middleware中间件

8.1 什么是中间件

  中间件是一种控制函数,相似插件,能控制流程中的init、validatesaveremove`方法

8.2 中间件的分类

  中间件分为两类

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

8.3 中间件特色

  一旦定义了中间件,就会在所有中间件执行完后执行其余操做,使用中间件能够雾化模型,避免异步操做的层层迭代嵌套

8.4 使用范畴

  1. 复杂的验证
  2. 删除有主外关联的doc
  3. 异步默认
  4. 某个特定动做触发异步任务,例如触发自定义事件和通知

  例如,能够用来作自定义错误处理

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

相关文章
相关标签/搜索