深入浅出mongoose

mongoose是nodeJS提供连接 mongodb的一个库. 此外还有mongoskin, mongodb(mongodb官方出品). 本人,还是比较青睐mongoose的, 因为他遵循的是一种, 模板式方法, 能够对你输入的数据进行自动处理. 有兴趣的同学可以去Mongoose官网看看.

初入mongoose

install mongoose

I’ve said that. 使用mongoose你需要有 nodeJS和mongodb数据库. 这两个东西, 前端宝宝们应该很清楚了》 下载mongoose:

  1. npm install mongoose --save

connect mongoose

下载好数据库之后,我们 来碰碰运气, 看你能不能连接上database. 首先,打开你的mongodb;

  1. mongod; //这里我已经将mongodb放在环境变量中了

数据库成功打开后: 在js文件中写入:

  1. 'use strict';
  2. const mongoose = require('mongoose');
  3. mongoose.connect('mongodb://localhost:27017/test');
  4. const con = mongoose.connection;
  5. con.on('error', console.error.bind(console, '连接数据库失败'));
  6. con.once('open',()=>{
  7. //成功连接
  8. })

(其实,懂得童鞋,你只要copy就行了.) OK,运气好的同学,此刻嘴角扬起45°的微笑. 运气差的同学, 出门左转google. 简单连接mongoose后,我们来看一看mongoose的基本构造吧.

understand mongoose

mongoose实际上,可以说是Oracle和mongodb的一个混合产物,但归根接地还是mongodb的. 这里我要祭出,我珍藏很久的对比图. 熟悉其他数据库的同学应该能很快明白的.

Oracle MongoDB Mongoose
数据库实例(database instance) MongoDB实例 Mongoose
模式(schema) 数据库(database) mongoose
表(table) 集合(collection) 模板(Schema)+模型(Model)
行(row) 文档(document) 实例(instance)
rowid _id _id
Join DBRef DBRef

通过上面的阐述,我们大概能知道了在Mongoose里面有哪几个基本概念.

  • Schema: 相当于一个数据库的模板. Model可以通过mongoose.model 集成其基本属性内容. 当然也可以选择不继承.
  • Model: 基本文档数据的父类,通过集成Schema定义的基本方法和属性得到相关的内容.
  • instance: 这就是实实在在的数据了. 通过 new Model()初始化得到.

他们各自间是怎样的关系呢? 下图可以清晰的说明, 以上3中实际上就是一个继承一个得到最后的数据.

我们先看一个demo吧:

  1. 'use strict';
  2. const mongoose = require('mongoose');
  3. mongoose.connect('mongodb://localhost:27017/test');
  4. const con = mongoose.connection;
  5. con.on('error', console.error.bind(console, '连接数据库失败'));
  6. con.once('open',()=>{
  7. //定义一个schema
  8. let Schema = mongoose.Schema({
  9. category:String,
  10. name:String
  11. });
  12. Schema.methods.eat = function(){
  13. console.log("I've eatten one "+this.name);
  14. }
  15. //继承一个schema
  16. let Model = mongoose.model("fruit",Schema);
  17. //生成一个document
  18. let apple = new Model({
  19. category:'apple',
  20. name:'apple'
  21. });
  22. //存放数据
  23. apple.save((err,apple)=>{
  24. if(err) return console.log(err);
  25. apple.eat();
  26. //查找数据
  27. Model.find({name:'apple'},(err,data)=>{
  28. console.log(data);
  29. })
  30. });
  31. })

到这里, 实际上, mongoose我们已经就学会了. 剩下就是看一看官方文档的API–CRUD相关操作. 如果,大家觉得意犹未尽的话,可以继续看下面的深入浅出. 而且, 下面会附上实际应用中, mongoose的写法.

深入浅出mongoose

这里,我们根据上面的3个概念深入的展开一下.

Schema

这实际上是,mongoose中最重要的一个theroy. schema 是用来定义 documents的基本字段和集合的. 在mongoose中,提供了Schema的类。 我们可以通过实例化他, 来实现创建 Schema的效果. 而不需要每次调用 mongoose.Schema()这个丑陋的API.

  1. // from mongoose author
  2. var mongoose = require('mongoose');
  3. var Schema = mongoose.Schema;
  4. var blogSchema = new Schema({
  5. title: String,
  6. author: String,
  7. body: String,
  8. comments: [{ body: String, date: Date }],
  9. date: { type: Date, default: Date.now },
  10. hidden: Boolean,
  11. meta: {
  12. votes: Number,
  13. favs: Number
  14. }
  15. });

Schema 之所以能够定义documents, 是因为他可以限制你输入的字段及其类型. mongoose支持的基本类型有:

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

其中, Mixed和ObjectId是mongoose中特有的,ObjectId实际上就是**_id**的一个映射. 同样,mongoose里面有着和所有大众数据库一样的东西. 索引 – indexs

mongoose 设置索引

这里设置索引分两种,一种设在Schema filed, 另外一种设在 Schema.index 里.

  1. //在field 设置
  2. var animalSchema = new Schema({
  3. name: String,
  4. type: String,
  5. tags: { type: [String], index: true }
  6. });
  7. //在Schema.index中设置.
  8. animalSchema.index({ name: 1, type: -1 });
  9. //1 表示正序, -1 表示逆序

实际上,两者效果是一样的. 看每个人的喜好了. 不过推荐直接在Schema level中设置, 这样分开能够增加可读性. 不过,官方给出了一个建议, 因为在创建字段时, 数据库会自动根据自动排序(ensureIndex). 有可能严重拖慢查询或者创建速度,所以一般而言,我们需要将该option 关闭.

  1. mongoose.connect('mongodb://user:pass@localhost:port/database', { config: { autoIndex: false } }); //真心推荐
  2. // or
  3. mongoose.createConnection('mongodb://user:pass@localhost:port/database', { config: { autoIndex: false } }); //不推荐
  4. // or
  5. animalSchema.set('autoIndex', false); //推荐
  6. // or
  7. new Schema({..}, { autoIndex: false }); //懒癌不推荐

另外, Schema 另一大特色就是其methods. 我们可以通过定义其methods,访问到实际上的所有内容.

定义Schema.methods

使用的方法很简单,就是使用 .methods即可.

  1. // 定义一个schema
  2. var freshSchema = new Schema({ name: String, type: String });
  3. // 添加一个fn.
  4. animalSchema.methods.findSimilarTypes = function (cb) {
  5. //这里的this指的是具体document上的this
  6. return this.model('Animal').find({ type: this.type }, cb);
  7. }
  8. // 实际上,我们可以通过schema绑定上,数据库操作的所有方法.
  9. // 该method实际上是绑定在 实例的 doc上的

定义完methods和property之后, 就到了生成Model的阶段了.

实例Model

这里同样很简单,只需要 mongoose.model() 即可.

  1. //生成,model 类. 实际上就相当于我们的一个collection
  2. var Animal = mongoose.model('Animal', animalSchema);
  3. var dog = new Animal({ type: 'dog' });

但是, 这里有个问题. 我们在Schema.methods.fn 上定义的方法,只能在 new Model() 得到的实例中才能访问. 那如果我们想,直接在Model上调用 相关的查询或者删除呢?

绑定Model方法

同样很简单,使用 statics 即可.

  1. // 给model添加一个findByName方法
  2. animalSchema.statics.findByName = function (name, cb) {
  3. //这里的this 指的就是Model
  4. return this.find({ name: new RegExp(name, 'i') }, cb);
  5. }
  6. var Animal = mongoose.model('Animal', animalSchema);
  7. Animal.findByName('fido', function (err, animals) {
  8. console.log(animals);
  9. });

Mongoose 还有一个super featrue-- virtual property 该属性是直接设置在Schema上的. 但是,需要注意的是,VR 并不会真正的存放在db中. 他只是一个提取数据的方法.

  1. //schema基本内容
  2. var personSchema = new Schema({
  3. name: {
  4. first: String,
  5. last: String
  6. }
  7. });
  8. // 生成Model
  9. var Person = mongoose.model('Person', personSchema);
  10. //现在我们有个需求,即,需要将first和last结合输出.
  11. //一种方法是,使用methods来实现
  12. //schema 添加方法
  13. personSchema.methods.getName = function(){
  14. return this.first+" "+this.last;
  15. }
  16. // 生成一个doc
  17. var bad = new Person({
  18. name: { first: 'jimmy', last: 'Gay' }
  19. });
  20. //调用
  21. bad.getName();

但是,像这样,仅仅这是为了获取一个属性, 实际上完全可以使用虚拟属性来实现.

  1. //schema 添加虚拟属性
  2. personSchema.virtual('fullName').get(function(){
  3. return this.first+" "+this.last;
  4. })
  5. //调用
  6. bad.fullName; //和上面的方法的结果是完全一致的

而且,经过测试, 使用fn实现的返回,比VR 要慢几十倍. 一下是测试结果:

  1. console.time(1);
  2. bad.getName();
  3. console.timeEnd(1);
  4. console.time(2);
  5. bad.fullName;
  6. console.timeEnd(2);
  7. //结果为:
  8. 1: 4.323ms; //method
  9. 2: 0.253ms // VR

最后再补充一下,Schema中初始化的相关参数.

Schema参数 在 new Schema([options]) 中,我们需要设置一些相关的参数.

  • safe: 用来设置安全模式. 实际上,就是定义入库时数据的写入限制. 比如写入时限等.
  1. //使用安全模式. 表示在写入操作时,如果发生错误,也需要返回信息.
  2. var safe = true;
  3. new Schema({ .. }, { safe: safe });
  4. // 自定义安全模式. w为写入的大小范围. wtimeout设置写入时限. 如果超出10s则返回error
  5. var safe = { w: "majority", wtimeout: 10000 };
  6. new Schema({ .. }, { safe: safe });
  • toObject: 用来表示在提取数据的时候, 把documents 内容转化为Object内容输出. 一般而言只需要设置getters为true即可.
  1. schema.set('toObject', { getters: true });
  2. var M = mongoose.model('Person', schema);
  3. var m = new M({ name: 'Max Headroom' });
  4. //实际打印出来的就是一个Object类型
  5. console.log(m); // { _id: 504e0cd7dd992d9be2f20b6f, name: 'Max Headroom is my name' }
  • toJSON: 该是和toObject一样的使用. 通常用来把 documents 转化为Object. 但是, 需要显示使用toJSON()方法,否则,不会起作用. 实际上,没什么卵用…

看一下总结图谱: 

看完schema之后,我们需要了解一下 model的内容.

Model

实际上,Model才是操作数据库最直接的一块内容. 我们所有的CRUD就是围绕着Model 展开的. ok. 还记得,我们是怎样创建一个model的吗?

model的创建

model的创建实际上就是方法的copy. 将schema上的方法,copy到model上. 只是copy的位置不一样, 一部分在prototype上, 一部分在constructor中.

  1. //from mongoosejs
  2. var schema = new mongoose.Schema({ name: 'string', size: 'string' });
  3. var Tank = mongoose.model('Tank', schema);

这里,我们一定要搞清楚一个东西. 实际上, mongoose.model里面定义的第一个参数,比如’Tank’, 并不是数据库中的, collection. 他只是collection的单数形式, 实际上在db中的collection是’Tanks’.

ok, 我们现在已经有了一个基本的Model. 但并没有什么x用. 接下来, 正式到了 dry goods(干货) 时间.

model 的子文档操作 这个就厉害了. 本来mongodb是没有关系的. 但是, mongoose提供了children字段. 让我们能够轻松的在表间建立关系. 现在,我们来创建一个子域:

  1. var childSchema = new Schema({ name: 'string' });
  2. var parentSchema = new Schema({
  3. children: [childSchema] //指明sub-doc的schema
  4. });
  5. //在创建中指明doc
  6. var Parent = mongoose.model('Parent', parentSchema);
  7. var parent = new Parent({ children: [{ name: 'Matt' }, { name: 'Sarah' }] })
  8. parent.children[0].name = 'Matthew';
  9. parent.save(callback);

现在, 我们就已经创建了3个table. 一个parent 包含了 两个child 另外,如果我们想要查询指定的doc。 则可以使用 id()方法.

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

子文档的CRUD, 实际上就是数组的操作, 比如push,unshift,remove,pop,shift等

  1. parent.children.push({ name: 'Liesl' });

mongoose还给移除提供了另外一个方法–remove:

  1. var doc = parent.children.id(id).remove();

如果你忘记添加子文档的话,可以在外围添加, 但是字段必须在Schema中指定

  1. var newdoc = parent.children.create({ name: 'Aaron' });

model的CRUD操作

model的创建 关于model的创建,有两种方法, 一种是使用实例创建,另外一种是使用Model类创建.

  1. var Tank = mongoose.model('Tank', yourSchema);
  2. var small = new Tank({ size: 'small' });
  3. //使用实例创建
  4. small.save(function (err) {
  5. if (err) return handleError(err);
  6. // saved!
  7. })
  8. //使用Model类创建
  9. Tank.create({ size: 'small' }, function (err, small) {
  10. if (err) return handleError(err);
  11. // saved!
  12. })

上面已经完美的介绍创建的方法了. 另外,官方给出一个提醒: 由于mongoose, 会自身连接数据库并断开. 如果你手动连接, 则创建model的方式需要改变.

  1. // 自己并没有打开连接:
  2. //注意,这里只是连接,并没有创建connection
  3. mongoose.connect('mongodb://localhost:27017/test');
  4. //手动创建连接:
  5. var connection = mongoose.createConnection('mongodb://localhost:27017/test');
  6. var Tank = connection.model('Tank', yourSchema);

然后, 下面的API还是一样的. 实际上,我们一般常用的写法为:

  1. const mongoose = require('mongoose');
  2. const Schema = mongoose.Schema;
  3. //设置连接位置
  4. mongoose.connect('mongodb://localhost:27017/test');
  5. var schema = new mongoose.Schema({ name: 'string', size: 'string' });
  6. var Tank = mongoose.model('Tank', schema);
  7. var small = new Tank({ size: 'small' });
  8. //使用实例创建
  9. small.save(function (err) {
  10. if (err) return handleError(err);
  11. console.log('创建成功');
  12. })

这样,就不用自己去手动管连接的问题了. 如果你,在后面想手动添加字段的话,可以使用.set方法.

  1. // 一个key/valye
  2. doc.set(path, value)
  3. //很多key/value
  4. doc.set({
  5. path : value,
  6. path2 : {
  7. path : value
  8. }
  9. })

model的query model的查找主要提供了以下的API,给我们进行操作. find, findById, findOne, or where 在mongodb中, query返回的数据格式一般都是为JSON的. 这点需要注意.

事实上,在mongoose中,query数据 提供了两种方式.

  • callback: 使用回调函数, 即, query会立即执行,然后返回到回调函数中.
  1. Person.findOne({ 'name.last': 'Ghost' }, 'name occupation', function (err, person) {
  2. if (err) return handleError(err);
  3. // get data
  4. })
  • query: 使用查询方法,返回的对象. 该对象是一个Promise, 所以可以使用 chain 进行调用.最后必须使用exec(cb)传入回调进行处理. cb 是一个套路, 第一个参数永远是err. 第二个就是返回的数据。
  1. Person.
  2. find({
  3. occupation: /host/,
  4. 'name.last': 'Ghost',
  5. age: { $gt: 17, $lt: 66 },
  6. likes: { $in: ['vaporizing', 'talking'] }
  7. }).
  8. limit(10).
  9. sort({ occupation: -1 }).
  10. select({ name: 1, occupation: 1 }).
  11. exec(callback);
  12. //如果没有查询到,则返回[] (空数组)
  13. // 如果你使用findOne, 没有的话则会返回 null

童鞋, 你觉得我会推荐哪种呢?

上面4个API, 3个使用方式都是一样的, 另外一个不同的是where. 他一样是用来进行query. 只是,写法和find系列略有不同.

where简介 where的API为: Model.where(path, [val]) path实际上就是字段, 第二个参数.val表示可以用来指定,path = val的数据内容, 你也可以不写, 交给后面进行筛选. 看一下对比demo吧:

  1. User.find({age: {$gte: 21, $lte: 65}}, callback);
  2. //等价于:
  3. User.where('age').gte(21).lte(65).exec(callback);

从上面的query中,我们可以看到有许多fn, 比如gte,lte,$gte,$lte. 这些是db提供给我们用来查询的快捷函数. 我们可以参考, mongoose给的参考: query Helper fn 这里,我们简要的了解下,基本的快捷函数.

name effect
select 添加需要显示的字段,需要的字段在字段后加上:1,不需要的加上0;<br/>query.select({ a: 1, b: 0 }); //显示a字段, 隐藏b字段<br/>不能和distinct方法一起使用
distinct 用来筛选不重复的值或者字段<br/>distinct(field). //筛选指定不重复字段的数据
$lt,$lte,$gt,$gte. 分别对应: <,<=,>,>=. 该字段是用在condition中的.如果,你想要链式调用,则需要使用<br/>lt,lte,ge,gte.<br/>eg:<br/> model.find({num:{$gt:12}},cb)<br/>model.where(‘num’).gt(12).exec(cb)
$in 查询包含键值的文档,<br/>model.find({name:{$in:[“jimmy”,“sam”]}}) //相当于匹配 jimmy或者sam
$nin 返回不匹配查询条件文档,都是指定数组类型<br/>model.find({name:{$nin:[“jimmy”,“sam”]}})
$ne 表示不包含指定值<br/>model.find({name:{$ne:“sam”}})
$or 表示或查询<br/>model.find({$or:[{ color: ‘red’ }, { status: ‘emergency’ }]})
$exits 表示键值是否存在;<br/>model.find({name:{$exits:true}})
$all 通常用来匹配数组里面的键值,匹配多个值(同时具有)<br/>$all:[“apple”,“banana”,“peach”]}
$size 用来查询数组的长度值<br/>model.find({name:{$size:3}}); 匹配name的数组长度为3
$slice 用来获取数组字段的内容:<br/>query.slice(‘comments’, 5)

ok~ 这上面就是比较常用的快捷函数. 另外还有一些游标集合的处理方法: 常用的就3个, limit,skip,sort.

  • **limit:**用来获取限定长度的内容.
  1. query.limit(20); //只返回前20个内容
  • skip: 返回,跳过指定doc后的值.
  1. query.skip(2);
  • sort: 用来设置根据指定字段排序. 可以设置为1:升序, -1:降序.
  1. query.sort({name:1,age:-1});

实际上, 关于query,我们需要了解的也就差不多了.

我们接下来,来看一下remove. mongoose remove 操作

官方提供的API,就是remove. 同样,移除的效果,我们可以使用两种方式实现。 一是回调函数, 二是, 链式调用.

  1. Model.find().remove({ name: 'Anne Murray' }).remove(fn);
  2. //或者直接添加回调
  3. Model.find().remove({ name: 'Anne Murray' },cb)

另外,我们可以直接在Model上调用. 因为remove也是Schema定义的statics方法. 而且, remove返回一个Promise对象

  1. product.remove().then(function (product) {
  2. ...
  3. });
  4. //或者直接传入回调
  5. Tank.remove({ size: 'large' }, function (err) {
  6. if (err) return handleError(err);
  7. // removed!
  8. });

最后,我们再看一下 update. 然后mongoose就基本结束了 update操作: 这里,我只说一下API就好. 因为update 比起上面来说,还是比较简单的. Model.update(conditions, doc, [options], [callback])

  • conditions: 就是query. 通过query获取到指定doc
  • doc: 就是用来替换doc内容的值.
  • options: 这块需要说一下.
    • safe (boolean) 是否开启安全模式 (default for true)
    • upsert (boolean) 如果没有匹配到内容,是否自动创建 ( default for false)
    • multi (boolean) 如果有多个doc,匹配到,是否一起更改 ( default for false)
    • strict (boolean) 使用严格模式(default for false)
    • overwrite (boolean) 匹配到指定doc,是否覆盖 (default for false)
    • runValidators (boolean): 表示是否用来启用验证. 实际上,你首先需要写一个验证. 关于如果书写,验证大家可以参考下文, validate篇(default for false)
  1. Model.update({age:18}, { $set: { name: 'jason borne' }}, {multi:true}, function (err, raw) {
  2. if (err) return handleError(err);
  3. console.log('raw 就是mongodb返回的更改状态的falg ', raw);
  4. //比如: { ok: 1, nModified: 2, n: 2 }
  5. });

其中的$set是,用来指明更新的字段.另外,mongoose还提供了一个:findByIdAndUpdate(id,doc[,options][,callback]); 方法. 关于mongoose的更新helper 函数. 童鞋们可以参考一下.mongoose官方文档.

validation

说完了,mongoose的body之后. 我们接着来看一下,官方给mongoose穿上的漂亮的衣服. 其中一件,比较吸引人的是–validation. 在你save数据之前, 你可以对数据进行一些列的validation. 来防止某天你傻不拉几的把数据完整性给破坏了. mongoose贴心的提供了几个built-in的验证函数.

  • required: 表示必填字段.
  1. new Schema({
  2. name: {
  3. type:String,
  4. required:[true,"name 是必须的"] //第二个参数是错误提示信息
  5. }
  6. })
  • min,max: 用来给Number类型的数据设置限制.
  1. var breakfastSchema = new Schema({
  2. eggs: {
  3. type: Number,
  4. min: [6, 'Too few eggs'],
  5. max: 12
  6. }
  7. });
  • enum,match,maxlength,minlength: 这些验证是给string类型的. enum 就是枚举,表示该属性值,只能出席那那些. match是用来匹配正则表达式的. maxlength&minlength 显示字符串的长度.
  1. new Schema({
  2. drink: {
  3. type: String,
  4. enum: ['Coffee', 'Tea']
  5. },
  6. food:{
  7. type: String,
  8. match:/^a/,
  9. maxlength:12,
  10. minlength:6
  11. }
  12. })

mongoose提供的helper fn就是这几种, 如果你想定制化验证. 可以使用custom validation.

  1. new Schema({
  2. phone: {
  3. type: String,
  4. validate: {
  5. validator: function(data) {
  6. return /\d{3}-\d{3}-\d{4}/.test(data);
  7. },
  8. message: '{VALUE} is not a valid phone number!' //VALUE代表phone存放的值
  9. },
  10. required: [true, 'User phone number required']
  11. }
  12. })

另外,还可以额外添加验证.

  1. var toySchema = new Schema({
  2. color: String,
  3. name: String
  4. });
  5. var validator = function (value) {
  6. return /blue|green|white|red|orange|periwinkle/i.test(value);
  7. };
  8. toySchema.path('color').validate(validator,
  9. 'Color `{VALUE}` not valid', 'Invalid color');

现在,我们已经设置了validation. 但是你不启用,一样没有什么卵用. 实际上, 我们也可以把validation当做一个中间件使用. mongoose 提供了两种调用方式. 一种是内置调用, 当你使用.save方法时,他会首先执行一次存储方法.

  1. cat.save(function(error) {
  2. //自动执行,validation
  3. });

另外一种是,手动验证–指定validate方法.

  1. //上面已经设置好user的字段内容.
  2. user.validate(function(error) {
  3. //error 就是验证不通过返回的错误信息
  4. assert.equal(error.errors['phone'].message,
  5. '555.0123 is not a valid phone number!');
  6. });
  7. });

事实上, 在validate时, 错误的返回信息有以下4个字段: kind, path, value, and message;

  • kind: 用来表示验证设置的第二个参数. 一般不用
  1. phone: {
  2. type: String,
  3. validate: {
  4. validator: function(data) {
  5. return /\d{3}-\d{3}-\d{4}/.test(data);
  6. },
  7. message: '{VALUE} is not a valid phone number!', //VALUE代表phone存放的值
  8. kind: "invalid phone"
  9. }
  10. })
  • path: 就是字段名
  • value: 你设置的错误内容
  • message: 提示错误信息 看一个整体demo吧:
  1. var validator = function (value) {
  2. return /blue|green|white|red|orange|periwinkle/i.test(value);
  3. };
  4. Toy.schema.path('color').validate(validator,
  5. 'Color `{VALUE}` not valid', 'Invalid color'); //设置了message && kind
  6. var toy = new Toy({ color: 'grease'});
  7. toy.save(function (err) {
  8. // err is our ValidationError object
  9. // err.errors.color is a ValidatorError object
  10. assert.equal(err.errors.color.message, 'Color `grease` not valid'); //返回message
  11. assert.equal(err.errors.color.kind, 'Invalid color');
  12. assert.equal(err.errors.color.path, 'color');
  13. assert.equal(err.errors.color.value, 'grease');
  14. assert.equal(err.name, 'ValidationError');
  15. //访问color 也可以直接上 errors["color"]进行访问.
  16. });

在Model.update那一节有个参数–runValidators. 还没有详细说. 这里, 展开一下. 实际上, validate一般只会应用在save上, 如果你想在update使用的话, 需要额外的trick,而runValidators就是这个trick.

  1. var opts = { runValidators: true };
  2. Test.update({}, update, opts, function(error) { //额外开启runValidators的验证
  3. // There will never be a validation error here
  4. });

我们来看一下基本总结吧: 

population

originally, mongodb 本来就是一门非关系型数据库。 但有时候,我们又需要联合其他的table进行数据查找。 这时候, 一般的做法就是实现两次查询,效率我就呵呵了.
此时, mongoose 说了一句: 麻麻, 我已经都把脏活帮你做好了. 感动~ 有木有~ 这就是mongoose提供的 population. 用来连接多表数据查询. 一般而言, 我们只要提供某一个collection的_id , 就可以实现完美的联合查询. population 用到的关键字是: ref 用来指明外联的数据库的名字. 一般,我们需要在schema中就定义好.

  1. var mongoose = require('mongoose')
  2. , Schema = mongoose.Schema
  3. var personSchema = Schema({
  4. _id : Number,
  5. name : String,
  6. age : Number,
  7. stories : [{ type: Schema.Types.ObjectId, ref: 'Story' }]
  8. });
  9. var storySchema = Schema({
  10. _creator : { type: Schema.Types.ObjectId, ref: 'Person' },
  11. title : String
  12. });

这里就指明了, 外联数据表的应用关系 personSchema <stories> By _id => Story storySchema <_creator> By _id => Person 实际上, 就是通过_id的相互索引即可. 这里需要说明的是, _id 应该是某个具体model的id.

我们来看一下, 接下来应该如何利用population实现外联查询.

  1. const sam = new Person({
  2. name: 'sam',
  3. _id: 1,
  4. age: 18,
  5. stories: []
  6. });
  7. sam.save((err,sam)=>{
  8. if(err) return err;
  9. let story = new Story({
  10. _creator:sam._id,
  11. title:"喜剧之王"
  12. })
  13. })
  14. Story.findOne({title:"喜剧之王"}).populate('_creator').exec((err,story)=>{
  15. if(err)console.log(err);
  16. console.log(story._creator.name);
  17. })
  18. //使用populate来指定,外联查询的字段, 而且该值必须是_id才行

现在es6时代的来临, generator , async/await 盛行. 也带来另外一种书写方式–middleware. 在mongoose也有这个hook, 给我们使用. (说实话, 有点像AOP)

mongoose && middleware

mongoose里的中间件,有两个, 一个是pre, 一个是post.

  • pre: 在指定方法执行之前绑定。 中间件的状态分为 parallel和series.
  • post: 相当于事件监听的绑定

这里需要说明一下, 中间件一般仅仅只能限于在几个方法中使用. (但感觉就已经是全部了)

  • doc 方法上: init,validate,save,remove;
  • model方法上: count,find,findOne,findOneAndRemove,findOneAndUpdate,update

pre

我们来看一下,pre中间件是如何绑定的.

  1. // series执行, 串行
  2. var schema = new Schema(..);
  3. schema.pre('save', function(next) {
  4. // exe some operations
  5. this.model.
  6. next(); // 这里的next()相当于间执行权给下一个pre
  7. });

在你调用 model.save方法时, 他会自动执行pre. 如果你想并行执行中间件, 可以设置为:

  1. schema.pre('save', true, function(next, done) {
  2. // 并行执行下一个中间件
  3. next();
  4. });

post

相当于绑定啦~ post会在指定事件后触发

  1. schema.post('save', function(doc) {
  2. //在save完成后 触发.
  3. console.log('%s has been saved', doc._id);
  4. });

当save方法调用时, 便会触发post绑定的save事件. 如果你绑定了多个post。 则需要指定一下中间件顺序.

  1. schema.post('save', function(doc, next) {
  2. setTimeout(function() {
  3. console.log('post1');
  4. next();
  5. }, 10);
  6. });
  7. schema.post('save', function(doc, next) {
  8. console.log('post2');
  9. next();
  10. });

实际上,post触发的时间为:

  1. var schema = new Schema(..);
  2. schema.post('save', function (doc) {
  3. console.log('this fired after a document was saved');
  4. });
  5. var Model = mongoose.model('Model', schema);
  6. var m = new Model(..);
  7. m.save(function (err) {
  8. console.log('this fires after the `post` hook');
  9. });

另外,在post和find中, 是不能直接修改doc上的属性的. 即,像下面一样的,没有效果

  1. articleSchema.post('find',function(docs){
  2. docs[1].date = 1
  3. })
  4. docs[1].date 的值还是不变

不过可以使用虚拟属性,进行操作.

[转] 深入浅出mongoose-----包括mongoose基本所有操作,非常实用!!!!!的更多相关文章

  1. mongoose - 让node.js高效操作mongodb

    Mongoose库简而言之就是在node环境中操作MongoDB数据库的一种便捷的封装,一种对象模型工具,类似ORM,Mongoose将数据库中的数据转换为JavaScript对象以供你在应用中使用. ...

  2. MONGOOSE – 让NODE.JS高效操作MONGODB(转载)

    Mongoose库简而言之就是在node环境中操作MongoDB数据库的一种便捷的封装,一种对象模型工具,类似ORM,Mongoose将数据库中的数据转换为JavaScript对象以供你在应用中使用. ...

  3. 基于mongoose 的增删改查操作

    无论是基于robomongo 的可视化工具,亦或是基于 mongoose 的函数工具,只要是对 mongodb 的操作,第一步都是开启数据库. 开启mongodb 数据库 进入mongod所在目录 执 ...

  4. Mongoose: mpromise (mongoose's default promise library) is deprecated, plug in your own promise library instead: http://mongoosejs.com/docs/promises.html

    操作数据库的时候,老是提示:Mongoose: mpromise (mongoose's default promise library) is deprecated, plug in your ow ...

  5. 深入浅出Java并发包—原子类操作

    我们知道,JDK1.5以后引入了并发包(java.util.concurrent)用于解决多CPU时代的并发问题,而并发包中的类大部分是基于Queue的并发类,Queue在大多数情况下使用了原子类(A ...

  6. Hibernate深入浅出(九)持久层操作——数据保存&批量操作

      数据保存: 1)session.save session.save方法用于实体对象到数据库的持久化操作.也就是说,session.save方法调用与实体对象所匹配的Insert SQL,将数据插入 ...

  7. java对文件操作之实用

    创建文件 package com.pre; import java.io.File; public class WJ { public static void main(String[] args) ...

  8. JDBC操作简单实用了IOUtils

    package cn.itcast.demo4; import java.io.FileInputStream; import java.io.FileOutputStream; import jav ...

  9. python操作excel实用脚本

    import xlrd data = xlrd.open_workbook('/home/ppe/workspace/pythonwp/tianranqi_org.xls') table = data ...

随机推荐

  1. Linux网络底层收发探究【转】

    转自:https://blog.csdn.net/davion_zhang/article/details/51536807 本文为博主原创文章,未经博主允许不得转载. https://blog.cs ...

  2. CentOS7利用systemctl添加自定义系统服务【转】

    systemctl enable name.service 设置开机启 systemctl disable name.service 删除开机启动指令 systemctl list-units --t ...

  3. python 中的exec

    x = 10 expr = """ z = 30 sum = x + y + z print(sum) """ def func(): y ...

  4. python3+selenium框架设计09-生成测试报告

    使用HTMLTestRunner可以生成测试报告.HTMLTestRunner是unittest模块下的一个拓展,原生的生成报告样式比较丑,GitHub上有大佬优化过后的版本:GitHub地址.下载之 ...

  5. localstorage实现网页状态记录比如放音乐功能的实例

    <div class="music"> <a href="javascript:;" onclick="playPause();&q ...

  6. linux系统常用运维命令

    目录/文件处理命令 mkdir dirname         创建文件夹 mkdir -p /tmp/a/b         递归创建目录 rm -rf dirname         删除目录及内 ...

  7. web缓存服务器varnish-4.1.6的部署及配置详解

    web缓存服务器varnish-4.1.6的部署及配置详解 1.安装varnish4.1.6安装依赖 yum install -y autoconf automake jemalloc-devel l ...

  8. Anaconda安装新模块

    如果使用import导入的新模块没有安装,则会报错,下面是使用Anaconda管理进行安装的过程:1.打开Anaconda工具,如图: 2.可通过输入 conda list 查看已安装的模块 3.如果 ...

  9. mysql5.7 pxc

    pxc优点总结:可以达到时时同步,无延迟现象发生完全兼容MySQL对于集群中新节点的加入,维护起来很简单数据的强一致性不足之处总结:只支持Innodb存储引擎存在多节点update更新问题,也就是写放 ...

  10. Linux系统基础优化及常用命令

    Linux基础系统优化 引言没有,只有一张图. Linux的网络功能相当强悍,一时之间我们无法了解所有的网络命令,在配置服务器基础环境时,先了解下网络参数设定命令. ifconfig 查询.设置网卡和 ...