Node.js中的Mongoose实用笔记

mongoose官网  http://mongoosejs.com/

1.Mongoose学习参考文档

前言:本学习参考文档仅供参考,如有问题,师请雅正

##一、快速通道

###1.1 名词解释

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

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

  • Entity : 由Model创建的实体,他的操作也会影响数据库

注意

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

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

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

##1.2 准备工作

1.首先你必须安装MongoDB和NodeJS

2.在项目只能够创建一个数据库连接,如下:

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

3.打开本机localhosttest数据库时,我们可以监测是否有异常

    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都有能影响数据库的操作,但仍有区别,后面我们也会做解释

##二、新手指引

如果您还不清楚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
    });


2.node.js + express(ejs) + mongodb(mongoose) 增删改实例   

node 和 mongodb安装不做为本文讲解,想了解的朋友可以通过一下链接配置环境:

node 安装:http://www.infoq.com/cn/articles/nodejs-npm-install-config
Win7下MongoDB安装 :http://www.mkyong.com/mongodb/how-to-install-mongodb-on-windows/

MongoDB 安装步骤总结:

1、解压目录到d mongodb

2、安装目录的下新建文件mongo.config文件

##store data here
dbpath=D:\mongodb\data
 
##all output go here
logpath=D:\mongodb\log\mongo.log
 
##log read and write operations
diaglog=3
3d:\mongodb\bin>mongod --config D:\mongodb\mongo.config
4d:\mongodb\bin> mongod --config D:\mongodb\mongo.config --install

net start MongoDB
net stop MongoDB
mongod --remove
1、创建node项目

express -e demo

2、初始化相关组件

npm install
// 初始化ejs
npm install ejs
// 初始化express组件
npm install express
// 初始化mongdb组合的组件 
npm install mongoose
npm install express-mongoose
// node 默认启动后修改文件,页面不会立即体现,通过该组件可以使修改的文件实时在页面体现出来
npm install supervisor

3、运行app.js,访问:http://localhost:3000,查看页面,如看到以下页面则说明页面访问成功:

4、此时项目默认使用*.ejs作为页面的模板,为了方便我们吧ejs的后缀修改html
a) 修改app.js中内容:

var ejs = require('ejs'); //增加

app.engine('html',ejs.__express); //增加

//将app.set('view engine', 'ejs'); 修改为app.set('view engine', 'html');

 
b) 重命名views下的index.ejs为index.html
 
5、重新启动服务访问,如果页面可以正常显示则说明修改成功。

6、现在开始写增删改查的详细方法

1)修改路由(app.js)

app.get('/add.html',routes.add); //跳转到添加页面 app.post('/add.html', routes.create);//添加记录 app.get('/del.html',routes.delById);//删除 app.get('/modify.html', routes.toModify); //跳转到修改页面 app.post('/modify.html', routes.modify);//修改数据

2)在routes目录下创建model.js文件,内容如下:

var mongoose = require('mongoose'); var Schema = mongoose.Schema; var demoSchema = new Schema({ uid : String, title:String, content:String, createTime : { type: Date, default: Date.now } }); exports.Demo = mongoose.model('Demo',demoSchema); //此处Demo会自动对应数据库中的demos表,不知道是不是可以修改,反正我是这个认为的。

3)修改index.js,添加逻辑方法

/* * GET home page. */ var mongoose = require('mongoose'); var model = require('./model'); var Demo = model.Demo; mongoose.connect('mongodb://localhost/monkey'); exports.index = function(req, res){ //查询所有数据,保存到demos中,在页面循环输出 Demo.find(function(err,docs){ res.render('index', { title:'Express Demo Example', demos:docs }); }); }; //跳转到添加页面 exports.add = function(req, res) { console.log('----here'); res.render('add.html', {title :'添加 demo list'}); }; //创建新纪录 exports.create = function(req, res){ var demo = new Demo({ uid : req.body.uid, title: req.body.title, content : req.body.content }); console.log('create----'); demo.save(function(err,doc){ console.log(doc); res.redirect('/'); }); }; // 根据id删除相应的记录 exports.delById = function(req, res) { var id = req.query.id; console.log('id = ' + id); if(id && '' != id) { console.log('----delete id = ' + id); Demo.findByIdAndRemove(id, function(err, docs) { console.log('delete-----'+ docs); res.redirect('/'); }); } }; // 查询对应修改记录,并跳转到修改页面 exports.toModify = function(req, res) { var id = req.query.id; console.log('id = ' + id); if(id && '' != id) { console.log('----delete id = ' + id); Demo.findById(id, function(err, docs){ console.log('-------findById()------' + docs); res.render('modify.html',{title:'修改ToDos',demo:docs}); }); }; }; //修改相应的值 exports.modify = function(req, res) { var demo = { uid : req.body.uid, title: req.body.title, content : req.body.content }; var id = req.body.id; //因为是post提交,所以不用query获取id if(id && '' != id) { console.log('----update id = ' + id + "," + demo); Demo.findByIdAndUpdate(id, demo,function(err, docs) { console.log('update-----'+ docs); res.redirect('/'); }); } };

4)index.html页面

</span><span class="pun" style="color:rgb(102,102,0)"><%=</span><span class="pln" style="color:rgb(0,0,0)"> title %></span><span class="tag" style="color:rgb(0,0,136)"> rel='stylesheet' href='/stylesheets/style.css' /> type="text/css"> table { border:1px solid green;} table thead tr th{ border:1px solid green;} table tbody tr td{ border:1px solid green;}

href="http://houzhiqingjava.blog.163.com/blog/add.html">增加

DEMO List

<% demos.forEach(function( demo ){ %> <% }); %>
id uid title content createTime 操作
<%=demo._id%> <%= demo.uid %> <%= demo.title %> <%= demo.content %> <%=demo.createTime%> <%=demo._id%>">Delete | <%=demo._id%>">Update

5)add.html页面

</span><span class="pun" style="color:rgb(102,102,0)"><%=</span><span class="pln" style="color:rgb(0,0,0)"> title %></span><span class="tag" style="color:rgb(0,0,136)"> rel='stylesheet' href='/stylesheets/style.css' /> type="text/css">

增加

method="post"> uid : type="text" name="uid"/>
title: type="text" name="title"/>
content: name="content">
type="submit" value="submit"/> type="reset" value="reset"/>

6) 修改页面modify.html

</span><span class="pun" style="color:rgb(102,102,0)"><%=</span><span class="pln" style="color:rgb(0,0,0)"> title %></span><span class="tag" style="color:rgb(0,0,136)"> rel='stylesheet' href='/stylesheets/style.css' />

修改

method="post"> uid : <%=demo.uid%>"/>
title:<%=demo.title%>"/>
content: name="content"><%=demo.content%>
<%=demo._id%>"/> type="submit" value="submit"/> type="reset" value="reset"/>


到此,增删改查已经完成,启动项目即可。

3.Mongoose--让Node.js更好的操作MongoDB数据库

Mongoose是什么

Mongoose是MongoDB的一个对象模型工具,可以工作于异步环境下。

定义一个模型很容易:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
var Comments =  new Schema({
     title     : String
   , body      : String
   , date      : Date}); var BlogPost =  new Schema({
     author    : ObjectId
   , title     : String
   , body      : String
   , date      : Date
   , comments  : [Comments]
   , meta      : {
         votes : Number
       , favs  : Number
     }});mongoose.model( 'BlogPost' , BlogPost);
回到顶部

安装

推荐通过NPM方式安装:

$ npm install mongoose

或者,你可以从Github仓库中获取代码,然后解压:

$ git clone git@github.com:LearnBoost/mongoose.git support/mongoose/
// 将模块添加至NodeJS可以找到的环境路径中require.paths.unshift('support/mongoose/lib');

然后就可以在应用中将mongoose模块包含进来

require('mongoose');
回到顶部

连接到MongoDB数据库

首先,我们需要定义一个连接。如果你的应用只使用一个数据库,可以使用mongoose.connect,如果需要创建附加数据库连接,使用mongoose.createConnection。

connect和createConnection都能连接mongodb数据库,支持以URI或参数(host,database,port)的形式。

?
1
var mongoose = require( 'mongoose' );mongoose.connect( 'mongodb://www.csser.com/csser.com_database' );

连接一旦建立成功,该连接实例的open事件就被触发。如果你使用的是mongoose.connect方式,连接对象为mongoose.connection;否则,mongoose.createConnection返回的是Connection对象。

切记!Mongoose在与数据库真正建立连接之前便缓存了所有的命令,这就意味着你在定义模型、执行查询时不必非要确认与MongoDB数据库的连接是否已经建立。(一回@CSSer注:异步是MongoDB等与传统数据库的重大区别)

回到顶部

定义模型

模型是通过模式接口(Schema interface)定义的,如:

?
1
2
3
4
5
6
var Schema = mongoose.Schema
   , ObjectId = Schema.ObjectId; var BlogPost =  new Schema({
     author    : ObjectId
   , title     : String
   , body      : String
   , date      : Date});

除了定义文档结构和你要存储的数据类型外,模式(Schema)还用于以下定义:

  • Validators (异步和同步)
  • Defaults - 默认值
  • Getters
  • Setters
  • Indexes - 索引
  • Middleware - 中间件
  • Methods definition - 方法定义
  • Statics definition - 静态定义
  • Plugins - 插件

下面的代码向我们展示了这些功能的一部分:

?
1
2
3
4
5
6
7
8
var Comment =  new Schema({
     name  :  { type: String,  default 'hahaha' }
   , age   :  { type: Number, min: 18, index:  true }
   , bio   :  { type: String, match: /[a-z]/ }
   , date  :  { type: Date,  default : Date.now }}); // 定义setterComment.path('name').set(function (v) {
   return v.capitalize();}); // 定义中间件Comment.pre('save', function (next) {
     notify( this .get( 'email' ));
     next();});

你可以查看几乎包含所有模型定义的 示例 。

回到顶部

访问模型

当通过mongoose.model('ModelName', mySchema)定义了一个模型之后,我们可以通过相同的函数来访问它:

var myModel = mongoose.model('ModelName');

接下来我们可以将模型的实例保存下来:

var instance = new myModel();instance.my.key = 'csser';instance.save(function (err) {   //});

或者我们也可以从同样的的集合(collection)中找到文档(documents):

myModel.find({}, function (err, docs) {   // docs.forEach});

也可以调用findOne, findById, update等等方法,更多的细节请阅读 API文档 。

回到顶部

嵌入文档

还记得在第一个示例的代码片段中,我们在模式中定义了一个键(key):

comments: [Comments]

这里的Comments是我们已经创建的模式(Schema),这就是说创建嵌入文档跟下面的代码看起来一样简单:

?
1
2
3
4
// 重新获得模型var BlogPost = mongoose.model('BlogPost');// 创建一篇博客日志
var post =  new BlogPost(); // 创建一个评论
post.comments.push({ title:  'My comment for csser.com' });post.save(function (err) {
if (!err) console.log( 'Success!' );});

用同样的方式删除模式:

?
1
2
3
4
5
6
7
BlogPost.findById(myId, function (err, post) {
   if (!err) {
     post.comments[ 0 ].remove();
     post.save(function (err) {
       // do something
     });
   }});

嵌入文档拥有与模型相同的功能,包括Defaults、validators、middleware等。当发生错误时,它会冒泡到save()错误回调函数,这里错误处理是一个单元。

Mongoose interacts with your embedded documents in arrays atomically, out of the box.

回到顶部

中间件

中间件是Mongoose 1.0推出的最激动人心的功能之一,它让我们可以不用再受嵌套回调函数的折磨了。

中间件定义在模式级别(Schema level),当方法初始化时(文档与MongoDB数据初始化后)、保存数据时(文档或嵌入文档保存后)生效。

中间件有两种类型,它们由定义的函数签名确定(即函数接受的参数)。

顺序(Serial)中间件定义如下:

?
1
2
3
.pre(method, function (next) {
   // ...
})

当每个中间件调用下一个时,它们按顺序执行。

并行(Parallel)中间件提供更细粒度的控制,其定义如下:

?
1
2
3
.pre(method, function (next, done) {
   // ...
})

Parallel 中间件可以立刻next(),但是只有当所有parallel中间件调用done()之后最后的参数才被执行。

回到顶部

错误处理

如果任一个中间件调用next或done时抛出了错误,执行流会中断,错误会被作为参数传入回调函数。

例如:

?
1
2
3
4
schema.pre( 'save' , function (next) {
     // 发生了一些错误
     next( new Error( '有错误发生' ));}); // 接着...myModel.save(function (err) {
   // 错误可以来自某个中间件});
回到顶部

API 文档

可以在 http://mongoosejs.com 找到相关API文档。


你可能感兴趣的:(NodeJS)