why mongoose?

谢绝转载,原文地址在胡新敏的个人博客,原文链接:http://huxinmin.com
未经作者同意,谢绝转载,原文链接:http://huxinmin.com

原文出自[胡新敏的个人博客] 转载请保留原文链接:http://huxinmin.com

本文出自,原文链接:http://huxinmin.com

谢绝转载,原文地址在胡新敏的个人博客,原文链接:http://huxinmin.com

未经作者同意,谢绝转载,原文链接:http://huxinmin.com

谢绝转载,原文地址在胡新敏的个人博客,原文链接:http://huxinmin.com
本文出自,原文链接:http://huxinmin.com
  • 谢绝转载,原文地址在胡新敏的个人博客,原文链接:http://huxinmin.com
  • 本文原文地址,原文链接:http://huxinmin.com
  • 本文出自,原文链接:http://huxinmin.com
  • 原文出自[胡新敏的个人博客] 转载请保留原文链接:http://huxinmin.com

      本文禁止任何形式的非法采集,原文地址:胡新敏的个人博客,原文链接:http://huxinmin.com
  • 未经作者同意,谢绝转载,原文链接:http://huxinmin.com
  • 原文出自[胡新敏的个人博客] 转载请保留原文链接:http://huxinmin.com
    未经作者同意,谢绝转载,原文链接:http://huxinmin.com

    禁止非法采集,原文地址,原文链接:http://huxinmin.com

    原文出自[胡新敏的个人博客] 转载请保留原文链接:http://huxinmin.com

    本文禁止任何形式的非法采集,原文地址:胡新敏的个人博客,原文链接:http://huxinmin.com

    本文禁止任何形式的非法采集,原文地址:胡新敏的个人博客,原文链接:http://huxinmin.com

    未经作者同意,谢绝转载,原文链接:http://huxinmin.com

    谢绝转载,原文地址在胡新敏的个人博客,原文链接:http://huxinmin.com

    本文禁止任何形式的非法采集,原文地址:胡新敏的个人博客,原文链接:http://huxinmin.com

    未经作者同意,谢绝转载,原文链接:http://huxinmin.com

    本文出自,原文链接:http://huxinmin.com

    本文出自,原文链接:http://huxinmin.com

    谢绝转载,原文地址在胡新敏的个人博客,原文链接:http://huxinmin.com

    禁止非法采集,原文地址,原文链接:http://huxinmin.com
    本文禁止任何形式的非法采集,原文地址:胡新敏的个人博客,原文链接:http://huxinmin.com
    原文出自[胡新敏的个人博客] 转载请保留原文链接:http://huxinmin.com
    本文禁止任何形式的非法采集,原文地址:胡新敏的个人博客,原文链接:http://huxinmin.com
    未经作者同意,谢绝转载,原文链接:http://huxinmin.com

    原文出自[胡新敏的个人博客] 转载请保留原文链接:http://huxinmin.com

    本文原文地址,原文链接:http://huxinmin.com
  • 未经作者同意,谢绝转载,原文链接:http://huxinmin.com
  • 原文出自[胡新敏的个人博客] 转载请保留原文链接:http://huxinmin.com
    禁止非法采集,原文地址,原文链接:http://huxinmin.com

    本文原文地址,原文链接:http://huxinmin.com

    禁止非法采集,原文地址,原文链接:http://huxinmin.com

    本文禁止任何形式的非法采集,原文地址:胡新敏的个人博客,原文链接:http://huxinmin.com

    谢绝转载,原文地址在胡新敏的个人博客,原文链接:http://huxinmin.com

    谢绝转载,原文地址在胡新敏的个人博客,原文链接:http://huxinmin.com

    禁止非法采集,原文地址,原文链接:http://huxinmin.com

    本文出自,原文链接:http://huxinmin.com

  • 禁止非法采集,原文地址,原文链接:http://huxinmin.com
  • 本文原文地址,原文链接:http://huxinmin.com未经作者同意,谢绝转载,原文链接:http://huxinmin.com未经作者同意,谢绝转载,原文链接:http://huxinmin.com

    本文原文地址,原文链接:http://huxinmin.com

    在官网上是这样说的,我们开发Mongoose是因为(开发者)写MongoDB的验证机制、类型转换与业务逻辑模板很麻烦。 针对为应用数据建模的问题,Mongoose 提供了一套直白的,基于模式的解决方案。包括了内建的类型转换、验证器、查询构造器、业务逻辑钩子等。

    简介

    mongoose是mongoDB的一个对象模型工具,是基于node-mongodb-native开发的mongoDB的nodejs驱动,可以在异步的环境下执行。同时它也是针对mongoDB操作的一个对象模型库,封装了mongoDB对文档的一些增删改查等常用方法,让nodejs操作mongoDB数据库变得更加容易。

    安装

    在安装mongoose之前我们需要安装mongodbnodejs,然后

    npm install mongoose
    

    然后开始建立连接并使用即可:

    var mongoose = require('mongoose');
    mongoose.connect('mongodb://user:pass@localhost:port/database',options);
    var conn = mongoose.connection;
    conn.on('connected', callback); //连接成功
    conn.on('disconnected', callback); //断开连接
    conn.on('connecting', callback); //连接中
    conn.on('disconnecting', callback); //断开连接中
    conn.on('error', callback); //连接异常
    

    Schemas

    mongoose中的schema对应于mongodb中的collection,并且定义了这个文档里面的数据模型。

     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
        }
      });
    //如果想在后期加入别的数据模型
    blogSchema.add({ name: 'string', color: 'string', price: 'number' });
    

    在定义数据模型的时候,您需要指定每个字段对应的数据类型,mongoose支持以下这些类型:

    • String
    • Number
    • Date
    • Buffer
    • Boolean
    • Mixed
    • ObjectId
    • Array
    • Decimal128
    • Map

    您可以直接指定数据的类型,还可以在指定类型的同时,设置一些选项:

    var schema1 = new Schema({
      test: String
    });
    var schema2 = new Schema({
      test: { 
         type: String,
         lowercase: true  //该选项只支持string类型
      }
    });
    

    公共选项列表如下:

    • required是否是必须填写的
    • default默认值,可以是一个值或者一个带返回值的函数
    • select布尔值,是否启用Mongodb的projection功能
    • validate函数,添加验证器
    • get添加getter函数
    • set添加setter函数
    • alias字符类型,定义一个virtual 序列选项列表如下:
    • index布尔值,是否为该键建立序列
    • unique布尔值,是否建议唯一序列
    • sparse布尔值,是否建立稀疏序列 字符串选项列表如下:
    • lowercase布尔值,转换为小写
    • uppercase布尔值,转换为大写
    • trim布尔值,是否使用trim()方法消除空白
    • match正则,检查是否匹配
    • enum数组,检测是否在数组中
    • minlength最小长度
    • maxlength最大长度

    注意的点:

    • 内置的js的Date方法改变mongoose的date类型数据时,保存时无效,必须先调用markModified方法
      var Assignment = mongoose.model('Assignment', { dueDate: Date });
      Assignment.findOne(function (err, doc) {
      doc.dueDate.setMonth(3);
      doc.save(callback); // 无效
      doc.markModified('dueDate');
      doc.save(callback); // 有效
      })
      
    • mixed类型的数据类型如果值改变,也需要先调用markModified方法
      person.anything = { x: [3, 4, { y: "changed" }] };
      person.markModified('anything');
      person.save();
      
    • 具化ObjectId的时候需要使用Schema.Types.ObjectId
      var ObjectId = mongoose.Schema.Types.ObjectId;
      var Car = new Schema({ driver: ObjectId });
      
    • map类型的数据需要get()set()来取值和设置值
      const user = new User({
      socialMediaHandles: {}
      });
      user.socialMediaHandles.set('github', 'vkarpov15');
      user.set('socialMediaHandles.twitter', '@code_barbarian');
      console.log(user.socialMediaHandles.get('github'));
      console.log(user.get('socialMediaHandles.twitter'));
      
    • schema.path()可以获得实例化的schema的类型
      var sampleSchema = new Schema({ name: { type: String, required: true } });
      console.log(sampleSchema.path('name'));
      // 输出如下:
      /**
      * SchemaString {
      *   enumValues: [],
      *   regExp: null,
      *   path: 'name',
      *   instance: 'String',
      *   validators: ...
      */
      

    我们可以在schema上定义一些methods,该方法可以在document上使用(注意不要使用箭头函数,否则无法获取this)

     var animalSchema = new Schema({ name: String, type: String });
    animalSchema.methods.findSimilarTypes = function(cb) {
        return this.model('Animal').find({ type: this.type }, cb);
    };
    //或者这样写
    animalSchema.method('findSimilarTypes ', function () {})
    animalSchema.method({findSimilarTypes : function () {}});
    //在Model上使用
    var Animal = mongoose.model('Animal', animalSchema);
    var dog = new Animal({ type: 'dog' });
    dog.findSimilarTypes(function(err, dogs) {
        console.log(dogs); // woof
    });
    

    我们还可以在schema上定义一些statics,该方法可以在model上使用:

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

    我们还可以为查找器增加一些链式帮助函数

     animalSchema.query.byName = function(name) {
        return this.where({ name: new RegExp(name, 'i') });
      };
    var Animal = mongoose.model('Animal', animalSchema);
      Animal.find().byName('fido').exec(function(err, animals) {
        console.log(animals);
      });
    

    mongoose默认会为每个schema建立一个自动索引,不过在生产环境建议关掉,有如下这些方法:

     mongoose.connect('mongodb://user:pass@localhost:port/database', { autoIndex: false });
      mongoose.createConnection('mongodb://user:pass@localhost:port/database', { autoIndex: false });
      animalSchema.set('autoIndex', false);
      new Schema({..}, { autoIndex: false });
    

    您还可以为schema设置虚拟的属性,该属性不会保存在数据库中,但是在你取值设置值的时候,可以分解或者合并属性值:

    var personSchema = new Schema({
        name: {
          first: String,
          last: String
        }
      });
      var Person = mongoose.model('Person', personSchema);
      var axl = new Person({
        name: { first: 'Axl', last: 'Rose' }
      });
    personSchema.virtual('fullName').get(function () {
      return this.name.first + ' ' + this.name.last;
    });
    console.log(axl.fullName); // Axl Rose
    

    注意要对虚拟属性进行toJSON() ortoObject()转换时需要传递{ virtuals: true }参数进来才行。

    你还可以使用alias为属性名设置别名

    var personSchema = new Schema({
      n: {
        type: String,
        alias: 'name'
      }
    })
    var person = new Person({ name: 'Val' });
    console.log(person); // { n: 'Val' }
    console.log(person.toObject({ virtuals: true })); // { n: 'Val', name: 'Val' }
    

    注意如果是嵌套属性,需要设置嵌套路径

    const parentSchema = new Schema({
      name: {
        f: {
          type: String,
          alias: 'name.first'
        }
      }
    });
    

    您还可以为schema设置选项,语法如下:

    new Schema({..}, options);
    //或者
    var schema = new Schema({..});
    schema.set(option, value);
    

    它有如下这些选项:

    • autoIndex布尔值,是否开启自动索引
    • bufferCommands布尔值,当连接挂掉重连之前是否缓冲命令
    • capped数值,使用固定大小collection
    • collection字符串,为collection设置不同的名字
    • id布尔值,关闭默认的虚拟属性id值(返回的其实是_id)
    • _id布尔值,关闭_id值(将会导致无法保存)
    • minimize布尔值,设为false会保存空对象
    • read设置query的read的优先级
    • writeConcern在schema级别设置write concern
    • safe遗留api,与writeConcern相同
    • shardKey设置分片key值
    • strict布尔值,规定在schema中没有定义的key不会被保存
    • strictQuery为filter设置query
    • toJSON转换为json对象与toObject相同,但是需要显示调用
    • toObject转换为Json对象
      var schema = new Schema({ name: String });
      schema.path('name').get(function (v) {
      return v + ' is my name';
      });
      schema.set('toJSON', { getters: true, virtuals: false });
      var M = mongoose.model('Person', schema);
      var m = new M({ name: 'Max Headroom' });
      schema.set('toObject', { getters: true });
      console.log(m.toJSON());// { _id: 504e0cd7dd992d9be2f20b6f, name: 'Max Headroom is my name' }
      console.log(m); // { _id: 504e0cd7dd992d9be2f20b6f, name: 'Max Headroom is my name' }
      
    • typeKey默认的,mongoose会将type字符串当做Mongoose的type解释,如果需要当做属性进行解释,需要设置typeKey
      var schema = new Schema({
      // Mongoose interpets this as 'loc is an object with 2 keys, type and coordinates'
      loc: { type: String, coordinates: [Number] },
      // Mongoose interprets this as 'name is a String'
      name: { $type: String }
      }, { typeKey: '$type' }); // A '$type' key means this object is a type declaration
      
    • validateBeforeSave手动控制是否在保存之前进行验证
      var schema = new Schema({ name: String });
      schema.set('validateBeforeSave', false);
      
    • versionKey自定义版本控制
    • collation设置校验
    • skipVersioning忽略版本管理
    • timestamps设置时间戳,默认为createdAt 和updatedAt
    • useNestedStrict设置嵌套schema的更新规则

    连接

    您可以使用下面的语法来连接mongodb:

    mongoose.connect('mongodb://username:password@host:port/database?options...',options);
    

    options有一下选项:

    • bufferCommands
    • user/pass
    • autoIndex
    • dbName
    • autoReconnect
    • reconnectTries
    • reconnectInterval
    • promiseLibrary
    • poolSize
    • bufferMaxEntries
    • connectTimeoutMS
    • socketTimeoutMS

    Models

    Models是用来创建和读写数据库的,它的一个实例就是document。

    var Tank = mongoose.model('Tank', yourSchema);
    //第一种方法
    var small = new Tank({ size: 'small' });
    small.save(function (err) {
      if (err) return handleError(err);
      // saved!
    });
    // 第二种方法
    Tank.create({ size: 'small' }, function (err, small) {
      if (err) return handleError(err);
      // saved!
    });
    // 第三种方法
    Tank.insertMany([{ size: 'small' }], function(err) {
    
    });
    

    注意每一个model都有对应的连接,如果使用的是mongoose.model(),则对应mongoose.connect('localhost', 'gettingstarted');如果使用的是自定义的连接,则使用

    var connection = mongoose.createConnection('mongodb://localhost:27017/test');
    var Tank = connection.model('Tank', yourSchema);
    

    常用查询操作:

    • deleteMany()
    • deleteOne()
    • find()
    • findById()
    • findByIdAndDelete()
    • findByIdAndRemove()
    • findByIdAndUpdate()
    • findOne()
    • findOneAndDelete()
    • findOneAndRemove()
    • findOneAndUpdate()
    • replaceOne()
    • updateMany()
    • updateOne()

    这些方法都有两种方法,一种是直接使用回调函数,另一种是使用.then()链式调用(不同于真正的promise,mongoose返回的查询不能使用多次then,否则会执行多次),当数据改变的时候,您还可以使用model.watch来进行观察:

    async function run() {
      // Create a new mongoose model
      const personSchema = new mongoose.Schema({
        name: String
      });
      const Person = mongoose.model('Person', personSchema, 'Person');
      // Create a change stream. The 'change' event gets emitted when there's a
      // change in the database
      Person.watch().
        on('change', data => console.log(new Date(), data));
      // Insert a doc, will trigger the change stream handler above
      console.log(new Date(), 'Inserting doc');
      await Person.create({ name: 'Axl Rose' });
    }
    2018-05-11T15:05:35.467Z 'Inserting doc'
    2018-05-11T15:05:35.487Z 'Inserted doc'
    2018-05-11T15:05:35.491Z { _id: { _data: ... },
      operationType: 'insert',
      fullDocument: { _id: 5af5b13fe526027666c6bf83, name: 'Axl Rose', __v: 0 },
      ns: { db: 'test', coll: 'Person' },
      documentKey: { _id: 5af5b13fe526027666c6bf83 } }
    

    您不仅可以把查询的条件一次性全部写在查询json中,还可以使用链式方式书写,下面的写法都是相等的:

    Person.
      find({
        occupation: /host/,
        'name.last': 'Ghost',
        age: { $gt: 17, $lt: 66 },
        likes: { $in: ['vaporizing', 'talking'] }
      }).
      limit(10).
      sort({ occupation: -1 }).
      select({ name: 1, occupation: 1 }).
      exec(callback);
    // 相等的写法
    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);
    

    您还可以为查询方法设置一些钩子函数,使用cursor()即可,有两种方法,第一种使用stream调用,第二种使用next手动调用:

    // There are 2 ways to use a cursor. First, as a stream:
    Thing.
      find({ name: /^hello/ }).
      cursor().
      on('data', function(doc) { console.log(doc); }).
      on('end', function() { console.log('Done!'); });
    // Or you can use `.next()` to manually get the next doc in the stream.
    // `.next()` returns a promise, so you can use promises or callbacks.
    var cursor = Thing.find({ name: /^hello/ }).cursor();
    cursor.next(function(error, doc) {
      console.log(doc);
    });
    

    subdocuments

    documents代表存储在mongodb中的实际数据,每一个document都是model的一个实例。subdocuments就是嵌套在其他documents中的子文档。mongoose中有两种子文档类型,一种是数组,一种是单个文档:

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

    subdocuments和documents之间的主要不同就是,subdocuments不单独保存,只要父级documents保存了,它就会被保存。子文档的pre('save')pre('validate')会在父文档的pre('save')之前,pre('validate')之后执行。您可以使用_id查找子文档:

    var doc = parent.children.id(_id);
    

    您可以使用push,unshift,addToSet来对子文档数组操作,或者直接使用create方法:

    var Parent = mongoose.model('Parent');
    var parent = new Parent;
    // create a comment
    parent.children.push({ name: 'Liesl' });
    var subdoc = parent.children[0];
    console.log(subdoc) // { _id: '501d86090d371bab2c0341c5', name: 'Liesl' }
    subdoc.isNew; // true
    parent.save(function (err) {
      if (err) return handleError(err)
      console.log('Success!');
    });
    //或者
    var newdoc = parent.children.create({ name: 'Aaron' });
    

    当您想要删除子文档时,可以使用remove方法:

    // Equivalent to `parent.children.pull(_id)`
    parent.children.id(_id).remove();
    // Equivalent to `parent.child = null`
    parent.child.remove();
    

    验证

    在mongoose中,数据的每次操作都需要进行验证,这样就可以防止一些恶意注入等危害。验证是在schema中定义的,且是middleware。

    Mongoose中有一些内置的验证器:

    • required
    • Numbersminmax验证器
    • Stringsenum, match, maxlength and minlength验证器

    如果想要自定义你自己的验证器,可以加入validate属性:

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

    您还有可以设置异步的验证器:

    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!';
              // First argument is a boolean, whether validator succeeded
              // 2nd argument is an optional error message override
              cb(phoneRegex.test(v), msg);
            }, 5);
          },
          // Default error message, overridden by 2nd argument to `cb()` above
          message: 'Default error message'
        },
        required: [true, 'User phone number required']
      }
    });
    

    如果想要在update()或者findOneAndUpdate()中开启验证的话,需要设置runValidators:

    var toySchema = new Schema({
      color: String,
      name: String
    });
    var Toy = db.model('Toys', toySchema);
    Toy.schema.path('color').validate(function (value) {
      return /blue|green|white|red|orange|periwinkle/i.test(value);
    }, 'Invalid color');
    var opts = { runValidators: true };
    Toy.update({}, { color: 'bacon' }, opts, function (err) {
      assert.equal(err.errors.color.message,
        'Invalid color');
    });
    

    update验证器和document验证器有几点区别:

    • update验证器this未定义,document验证器this指向该document,不过你可以在update验证器上通过设置context设置this指向该查询
    • update验证器只验证对应的路径
    • update验证器只在一些操作符时候运行,$set$unset$push (>= 4.8.0)$addToSet (>= 4.8.0)$pull (>= 4.12.0)$pullAll (>= 4.12.0)

    Middleware

    Mongoose有4种中间件:

    • document 中间件:
      • init
      • validate
      • save
      • remove
    • model 中间件:
      • count
      • find
      • findOne
      • findOneAndRemove
      • findOneAndUpdate
      • update
      • updateOne
      • updateMany
    • aggregate 中间件:
      • aggregate
    • query 中间件:
      • insertMany

    有两种钩子函数,一种是串行的,一种是并行的,在串行中,你可以使用next来执行下一步,或者返回一个promise函数,pre表示之前,post表示之后:

    var schema = new Schema(..);
    schema.pre('save', function(next) {
      // do stuff
      next();
    });
    schema.pre('save', function() {
      return doStuff().
        then(() => doMoreStuff());
    });
    // Or, in Node.js >= 7.6.0:
    schema.pre('save', async function() {
      await doStuff();
      await doMoreStuff();
    });
    

    并行的提供了更加细粒度的流控制:

    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和pre不会在update(), findOneAndUpdate()上执行。

    Populate

    population是一个自动把document中的路径转换为对应的document的过程,使用ref指向即可:

    var mongoose = require('mongoose');
    var Schema = mongoose.Schema;
    var personSchema = Schema({
      _id: Schema.Types.ObjectId,
      name: String,
      age: Number,
      stories: [{ type: Schema.Types.ObjectId, ref: 'Story' }]
    });
    var storySchema = Schema({
      author: { type: Schema.Types.ObjectId, ref: 'Person' },
      title: String,
      fans: [{ type: Schema.Types.ObjectId, ref: 'Person' }]
    });
    var Story = mongoose.model('Story', storySchema);
    var Person = mongoose.model('Person', personSchema);
    

    注意ObjectId, Number, String, and Buffer都是合法的ref类型,但是对于新手最好只使用ObjectId。

    然后在查询的时候,我们就可以populate将对应的数据整合进来:

    Story.
      findOne({ title: 'Casino Royale' }).
      populate('author').
      exec(function (err, story) {
        if (err) return handleError(err);
        console.log('The author is %s', story.author.name);
        // prints "The author is Ian Fleming"
      });
    

    如果只想整合一些域的话,可以在populate的第二个参数中写入要选择的字段,如果是多个的话可以用空格分开:

    Story.
      findOne({ title: /casino royale/i }).
      populate('author', 'name'). // only return the Persons name
      exec(function (err, story) {})
    

    如果想要整合多个ref的话,可以连续调用(如果对同一个ref设置多个,则只有最后一个生效)

    Story.
      find(...).
      populate('fans').
      populate('author').
      exec();
    

    如果想要更精细化的控制,还可以给populate传入一个对象:

    Story.
      find(...).
      populate({
        path: 'fans',
        match: { age: { $gte: 21 }},
        // Explicitly exclude `_id`, see http://bit.ly/2aEfTdB
        select: 'name -_id',
        options: { limit: 5 }
      }).
      exec();
    

    如果你想要populate文档下面的populate对象,则:

    User.
      findOne({ name: 'Val' }).
      populate({
        path: 'friends',
        // Get friends of friends - populate the 'friends' array for every friend
        populate: { path: 'friends' }
      });
    

    跨数据库populate:

    var eventSchema = new Schema({
      name: String,
      // The id of the corresponding conversation
      // Notice there's no ref here!
      conversation: ObjectId
    });
    var conversationSchema = new Schema({
      numMessages: Number
    });
    var db1 = mongoose.createConnection('localhost:27000/db1');
    var db2 = mongoose.createConnection('localhost:27001/db2');
    var Event = db1.model('Event', eventSchema);
    var Conversation = db2.model('Conversation', conversationSchema);
    Event.
      find().
      populate({ path: 'conversation', model: Conversation }).
      exec(function(error, docs) { /* ... */ });
    

    您还可以通过设置refPath来设置动态的ref:

    var userSchema = new Schema({
      name: String,
      connections: [{
        kind: String,
        item: { type: ObjectId, refPath: 'connections.kind' }
      }]
    });
    

    只在_id中设置ref不是最好的选择,您还可以在virtuals中进行设置:

    var PersonSchema = new Schema({
      name: String,
      band: String
    });
    var BandSchema = new Schema({
      name: String
    });
    BandSchema.virtual('members', {
      ref: 'Person', // The model to use
      localField: 'name', // 找到本地字段 `localField`与对应model字段`foreignField`值相等的document
      foreignField: 'band', 
      // If `justOne` is true, 'members' will be a single doc as opposed to
      // an array. `justOne` is false by default.
      justOne: false
    });
    

    注意toJSON()不包含在virtuals 中,所以需要手动设置:

    var BandSchema = new Schema({
      name: String
    }, { toJSON: { virtuals: true } });
    

    然后使用,注意foreignField必须包含在populate中:

    Band.
      find({}).
      populate({ path: 'members', select: 'name band' }).
      exec(function(error, bands) {
        // Works, foreign field `band` is selected
      });
    

    您还可以在中间件中使用populate:

    MySchema.pre('find', function() {
      this.populate('user');
    });
    MySchema.post('find', async function(docs) {
      for (let doc of docs) {
        if (doc.isPublic) {
          await doc.populate('user').execPopulate();
        }
      }
    });
    MySchema.post('save', function(doc, next) {
      doc.populate('user').execPopulate(function() {
        next();
      });
    });
    

    Discriminators

    Discriminators 是一个schema的继承机制,可以使得你拥有多个models使用了重叠的schema在同一个collection下面。例如你可以使用model.discriminator()来整合一个基础的schema和一个Discriminator schema:

    var options = {discriminatorKey: 'kind'};
    var eventSchema = new mongoose.Schema({time: Date}, options);
    var Event = mongoose.model('Event', eventSchema);
    // ClickedLinkEvent is a special type of Event that has
    // a URL.
    var ClickedLinkEvent = Event.discriminator('ClickedLink',
      new mongoose.Schema({url: String}, options));
    // When you create a generic event, it can't have a URL field...
    var genericEvent = new Event({time: Date.now(), url: 'google.com'});
    assert.ok(!genericEvent.url);
    // But a ClickedLinkEvent can
    var clickedEvent =
      new ClickedLinkEvent({time: Date.now(), url: 'google.com'});
    

    mongoose分辨不同的Discriminators 是通过__t来区分的,而且Discriminators 都可以很智能地通过find(), count(), aggregate()来找到数量:

    var event1 = new Event({time: Date.now()});
    var event2 = new ClickedLinkEvent({time: Date.now(), url: 'google.com'});
    var event3 = new SignedUpEvent({time: Date.now(), user: 'testuser'});
    var save = function (doc, callback) {
      doc.save(function (error, doc) {
        callback(error, doc);
      });
    };
    async.map([event1, event2, event3], save, function (error) {
      ClickedLinkEvent.find({}, function (error, docs) {
        assert.equal(docs.length, 1);
        assert.equal(docs[0]._id.toString(), event2._id.toString());
        assert.equal(docs[0].url, 'google.com');
      });
    });
    

    Discriminators 可以使用基础的schema的中间件,但是你也可以为它添加自己的中间件而不影响基础的schema:

    var options = {discriminatorKey: 'kind'};
    var eventSchema = new mongoose.Schema({time: Date}, options);
    var eventSchemaCalls = 0;
    eventSchema.pre('validate', function (next) {
      ++eventSchemaCalls;
      next();
    });
    var Event = mongoose.model('GenericEvent', eventSchema);
    var clickedLinkSchema = new mongoose.Schema({url: String}, options);
    var clickedSchemaCalls = 0;
    clickedLinkSchema.pre('validate', function (next) {
      ++clickedSchemaCalls;
      next();
    });
    var ClickedLinkEvent = Event.discriminator('ClickedLinkEvent',
      clickedLinkSchema);
    var event1 = new ClickedLinkEvent();
    event1.validate(function() {
      assert.equal(eventSchemaCalls, 1);
      assert.equal(clickedSchemaCalls, 1);
      var generic = new Event();
      generic.validate(function() {
        assert.equal(eventSchemaCalls, 2);
        assert.equal(clickedSchemaCalls, 1);
      });
    });
    

    当你使用Model.create()自动创建时,mongoose会自动为您选择适合的Discriminator进行创建:

    var Schema = mongoose.Schema;
    var shapeSchema = new Schema({
      name: String
    }, { discriminatorKey: 'kind' });
    var Shape = db.model('Shape', shapeSchema);
    var Circle = Shape.discriminator('Circle',
      new Schema({ radius: Number }));
    var Square = Shape.discriminator('Square',
      new Schema({ side: Number }));
    var shapes = [
      { name: 'Test' },
      { kind: 'Circle', radius: 5 },
      { kind: 'Square', side: 10 }
    ];
    Shape.create(shapes, function(error, shapes) {
      assert.ifError(error);
      assert.ok(shapes[0] instanceof Shape);
      assert.ok(shapes[1] instanceof Circle);
      assert.equal(shapes[1].radius, 5);
      assert.ok(shapes[2] instanceof Square);
      assert.equal(shapes[2].side, 10);
    });
    

    常用API

    • Model
      • Model.count()找到符合条件的文档数量,已经废弃
      • Model.countDocuments()找到符合条件的文档数量
      • Model.create()创建一个或多个文档,MyModel.create(docs)等同于多个new MyModel(doc).save()
      • Model.deleteMany()删除多个
      • Model.deleteOne()删除一个
      • Model.distinct()查找所有文档中某个字段的不重复值,返回去重后的字段所有值
      • Model.find()
      • Model.findById()
      • Model.findByIdAndDelete()
      • Model.findByIdAndRemove()
      • Model.findByIdAndUpdate()
      • Model.findOne()
      • Model.findOneAndDelete()
      • Model.findOneAndRemove()
      • Model.findOneAndUpdate()
      • Model.insertMany()
      • Model.populate()
      • Model.prototype.$where()创建一个查询
        Blog.$where('this.username.indexOf("val") !== -1').exec();
        
      • Model.prototype.remove()
      • Model.prototype.save()
      • Model.remove()
      • Model.replaceOne()
      • Model.update()更新文档但是不返回该数据
      • Model.updateMany()
      • Model.updateOne()
      • Model.where()查询
        User.where('age').gte(21).lte(65).exec(callback);
        
    • Document

      • Document.prototype.$ignore()不允许验证器,不保留改动某个字段
      • Document.prototype.$isDefault()判断某个字段是否设置默认值
      • Document.prototype.$isDeleted()判断是否移除
      • Document.prototype.depopulate()进行populate的逆运算
      • Document.prototype.get()取值
      • Document.prototype.invalidate()将字段标记为不合格,触发验证失败
      • Document.prototype.populate()整合ref
      • Document.prototype.populated()返回整合的ref的_id
      • Document.prototype.save()保存
      • Document.prototype.set()设置值
      • Document.prototype.toJSON()转换为json
      • Document.prototype.toObject()转换为plainObject
      • Document.prototype.update()更新文档
      • Document.prototype.validate()执行验证器
      • Document.prototype.validateSync()同步执行验证器
    • Query

      • Query.prototype.$where()执行一个函数或表达式查询
        query.$where('this.comments.length === 10 || this.name.length === 5')
        // or
        query.$where(function () {
        return this.comments.length === 10 || this.name.length === 5;
        })
        
      • Query.prototype.countDocuments()查询数量
      • Query.prototype.deleteMany()
      • Query.prototype.deleteOne()
      • Query.prototype.distinct()
      • Query.prototype.elemMatch()嵌套查询使用,替代find查询嵌套字段写path.path
      • Query.prototype.equals()查询时候值等于
      • Query.prototype.exec()执行查询
      • Query.prototype.exists()判断是否存在某个字段
      • Query.prototype.find()
      • Query.prototype.findOne()
      • Query.prototype.findOneAndDelete()
      • Query.prototype.findOneAndRemove()
      • Query.prototype.findOneAndUpdate()
      • Query.prototype.getQuery()返回查询条件,使用json格式表示出来
      • Query.prototype.getUpdate()返回更新条件,使用json格式表示
      • Query.prototype.gt()
      • Query.prototype.gte()
      • Query.prototype.lean()查询后的结果如果使用了lean将不再有save等doc的方法
      • Query.prototype.limit()
      • Query.prototype.lt()
      • Query.prototype.lte()
      • Query.prototype.nor()都不是
        query.nor([{ color: 'green' }, { status: 'ok' }])
        
      • Query.prototype.or()两个中一个
        query.or([{ color: 'red' }, { status: 'emergency' }])
        
      • Query.prototype.populate()
      • Query.prototype.regex()正则查询
      • Query.prototype.remove()
      • Query.prototype.select()选择需要展示或者排除的字段,如果是字符可以用+表示包含,-表示排除,对象可以用1表示包含,0表示排除
        query.select('a b');
        query.select('-c -d');
        query.select({ a: 1, b: 1 });
        query.select({ c: 0, d: 0 });
        
      • Query.prototype.set()
      • Query.prototype.setOptions()
      • Query.prototype.skip()
      • Query.prototype.slice()
      • Query.prototype.sort()排序,当你使用object时,可以指定asc, desc, ascending, descending, 1, and -1,如果是字符串,默认是升序,使用-来降序:
        // sort by "field" ascending and "test" descending
        query.sort({ field: 'asc', test: -1 });
        // equivalent
        query.sort('field -test');
        
      • Query.prototype.then()
      • Query.prototype.update()
      • Query.prototype.updateMany()
      • Query.prototype.updateOne()
      • Query.prototype.where()

    实例

    分页:

    model.find(queryCondition).limit(limit).skip(limit*page).exec(function(err, results){
          ...
        });
    

    参考资料