why mongoose?

在官网上是这样说的,我们开发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){
      ...
    });

参考资料