Mongoose基础入门

作者: nimw | 来源:发表于2018-08-03 09:10 被阅读35次

    一. 介绍

    1. MongoDB 是文档型数据库(Document Database),不是关系型数据库(Relational Database)。而Mongoose可以将MongonDB 数据库存储的文档(documents)转化为javascript 对象,然后可以直接进行数据的增删改查。
      注释:Mongoose是在node.js异步环境下对mongodb进行便捷操作的对象模型工具。Mongoose只能作为NodeJS的驱动,不能作为其他语言的驱动。
    2. Mongooose中有三个比较重要的概念,分别是SchemaModelentity。它们的关系是Schema生成ModelModel实例出entity
    3. Schema不具备操作数据库的能力。Modelentity都可对数据库操作造成影响,Modelentity更具操作性。
    4. Schema用于定义数据库的结构。每个Schema都会映射到mongodb中的一个collection
    5. Model是由Schema编译而成的构造器,具有抽象属性和行为,可以对数据库进行增删查改。Model的每一个实例entity就是一个文档document
      注释: Schema对应数据库中一个集合collection里文档document的数据结构。Model对应一个集合 collectionentityModel的实例,对应集合collection中的一个文档document

    二. 连接数据库

    1. 使用connect()方法连接数据库。
      语法: mongoose.connect(url, options)
      参数:options为可选参数,优先级高于urloptions可用选项如下:
    选项 含义
    db 数据库设置
    server 服务器设置
    replset 副本集设置
    user 用户名
    pass 密码
    auth 鉴权选项
    mongos 连接多个数据库
    1. 简单连接,传入url参数以及db数据库名称。
    mongoose.connect('mongodb://127.0.0.1/test');
    

    注意:默认端口为27017

    1. 传入用户名、密码、端口等参数。
    mongoose.connect('mongodb://username:password@host:port/database?options...');
    
    1. 通过mongoose.connection监听连接状态。
    var mongoose =  require('mongoose');
    
    mongoose.connect('mongodb://127.0.0.1:27017/test');
    
    mongoose.connection.on('connected', function() {
      console.log('MongoDB connected connected');
    })
    mongoose.connection.on('error', function() {
      console.log('MongoDB connected error');
    })
    mongoose.connection.on('disconnected', function() {
      console.log('MongoDB connected disconnected');
    })
    
    1. 避免多次连接
      新建一个mongoose.js:
    var mongoose = require("mongoose");
    mongoose.connect('mongodb://127.0.0.1:27017/test');
    module.exports = mongoose;
    

    每个module中,引用var mongoose = require('./mongoose.js')

    1. 断开连接
      语法:mongoose.disconnect()
    mongoose.connect('mongodb://127.0.0.1:27017/test');
    
    setTimeout(()=> {
      mongoose.disconnect();
    }, 2000);
    
    mongoose.connection.on('connected', function() {
      console.log('MongoDB connected connected');
    })
    mongoose.connection.on('error', function() {
      console.log('MongoDB connected error');
    })
    mongoose.connection.on('disconnected', function() {
      console.log('MongoDB connected disconnected');
    })
    

    三. Scheme

    1. Schema主要用于定义MongoDB中集合collection里文档document的结构。定义Schema非常简单,指定字段名和类型即可。
    2. Scheme支持以下8种类型
      String字符串、Number数字 、Date日期、Buffer二进制、Boolean布尔值、Mixed 混合类型、ObjectId对象ID、Array数组。
    3. 通过mongoose.Schema来调用Schema,然后使用new方法来创建schema对象。
    var mongoose = require('mongoose');
    var Schema = mongoose.Schema;
    
    var mySchema = 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
      }
    });
    
    1. 创建Schema对象时,声明字段类型有两种方法,一种是首字母大写的字段类型,另一种是引号包含的小写字段类型。
    var mySchema = new Schema({title:String, author:String});
    //或者 
    var mySchema = new Schema({title:'string', author:'string'});
    
    1. 如果需要在Schema定义后添加其他字段,可以使用add()方法。
    var MySchema = new Schema;
    MySchema.add({ name: 'string', color: 'string', price: 'number' });
    
    1. schema中设置timestampstrueschema映射的文档document会自动添加createdAtupdatedAt这两个字段,代表创建时间和更新时间。
    var UserSchema = new Schema(
      {...},
      { timestamps: true }
    );
    
    1. 每一个文档document都会被mongoose添加一个不重复的_id_id的数据类型不是字符串,而是ObjectID类型。如果在查询语句中要使用_id,则需要使用findById语句,而不能使用findfindOne语句。

    四. Model

    1. 模型Model是根据Schema编译出的构造器,或者称为类。
    2. 使用model()方法将Schema编译为Model
      语法:mongoose.model("模型名称", Scheme, "Collection名称(可选)")
      注意:如果未传第三个参数指定Collection集合名称。则Mongoose会将Collection集合名称设置为模型名称的小写版。如果名称的最后一个字符是字母,则会变成复数;如果名称的最后一个字符是数字,则不变。例如,如果模型名称为MyModel,则集合名称为mymodels;如果模型名称为Model1,则集合名称为model1
    var schema = new mongoose.Schema({ num:Number, name: String, size: String});
    var MyModel = mongoose.model('MyModel', schema);
    
    1. 生成实例entity
    var schema = new mongoose.Schema({ num:Number, name: String, size: String});
    var MyModel = mongoose.model('MyModel', schema);
    var doc = new MyModel({ size: 'small' });
    console.log(doc.size);//'small'
    
    1. 实例entity保存为文档document
      通过new Model()创建的实例entity,必须通过save()方法,才能将对应文档document保存到数据库的集合collection中。回调函数是可选项,第一个参数为err,第二个参数为保存的文档document对象。
      语法:save(function (err, doc) {})
    var mongoose =  require('mongoose');
    
    mongoose.connect('mongodb://test:test@127.0.0.1:27017/test');
    
    var schema = new mongoose.Schema({ num:Number, name: String, size: String});
    var MyModel = mongoose.model('MyModel', schema);
    var doc = new MyModel({ size: 'small' });
    doc.save(function (err,doc) {
      //{ __v: 0, size: 'small', _id: 5970daba61162662b45a24a1 }
      console.log(doc);
    })
    

    注释:① 一个实例Entiy对应一条文档document。② 实例entitysave方法只能够在文档document中保存scheme结构范围内的字段。

    五. 自定义方法

    1. 实例方法
      Model的实例entity有很多内置方法,例如 save。可以通过Schema对象的methods属性给entity自定义扩展方法。
    var schema = new mongoose.Schema({ num:Number, name: String, size: String });        
    schema.methods.findSimilarSizes = function(cb){
      return this.model('MyModel').find({size:this.size},cb);
    }
    
    var MyModel = mongoose.model('MyModel', schema);
    var doc1 = new MyModel({ name:'doc1', size: 'small' });
    var doc2 = new MyModel({ name:'doc2', size: 'small' });
    var doc3 = new MyModel({ name:'doc3', size: 'big' });
    doc1.save();
    doc2.save();
    doc3.save();
    setTimeout(function(){
        doc1.findSimilarSizes(function(err,docs){
            docs.forEach(function(item,index,arr){
                //doc1
                //doc2
                  console.log(item.name)        
            })
        })  
    },0) 
    
    1. 静态方法
      可以通过Schema对象的statics属性给 Model添加静态方法。
    var schema = new mongoose.Schema({ num:Number, name: String, size: String });
    schema.statics.findByName = function(name,cb){
        return this.find({name: new RegExp(name,'i')},cb);
    }
    
    var MyModel = mongoose.model('MyModel', schema);
    var doc1 = new MyModel({ name:'doc1', size: 'small' });
    var doc2 = new MyModel({ name:'doc2', size: 'small' });
    var doc3 = new MyModel({ name:'doc3', size: 'big' });
    doc1.save();
    doc2.save();
    doc3.save();
    setTimeout(function(){
        MyModel.findByName('doc1',function(err,docs){
            //[ { _id: 5971e68f4f4216605880dca2,name: 'doc1',size: 'small',__v: 0 } ]
            console.log(docs);
        })
    },0)
    

    注释:静态方法是通过Schema对象的statics属性给model添加方法;实例方法是通过Schema对象的methods是给entity添加方法。

    1. 查询方法
      通过schema对象的query属性,给model添加查询方法。
    var schema = new mongoose.Schema({ age:Number, name: String});        
    schema.query.byName = function(name){
        return this.find({name: new RegExp(name)});
    }
    
    var temp = mongoose.model('temp', schema);   
    temp.find().byName('huo').exec(function(err,docs){
        //[ { _id: 5971f93be6f98ec60e3dc86c, name: 'huochai', age: 27 },
        // { _id: 5971f93be6f98ec60e3dc86e, name: 'huo', age: 30 } ]
        console.log(docs);
    }) 
    

    注释:查询方法需要有 .find() 作为开头。
    问题:静态方法与查询方法的本质区别?

    六. 新增文档

    1. mongoose提供了三种新增文档document的方法:
      (1) entitysave()方法
      (2) modelcreate()方法
      (3) modelinsertMany()方法
    2. entitysave()方法
      save([options], [options.safe], [options.validateBeforeSave], [fn])
    mongoose.connect('mongodb://test:test@127.0.0.1:27017/test');
    
    var schema = new mongoose.Schema({name: String, age: Number});
    var model = mongoose.model('temp', schema);
    var entity = new model({name: 'john', age: 18});
    entity.save((err, doc) => {
      //{ _id: 5b63a49e8910503426acd587, name: 'john', age: 18, __v: 0 }
      console.log(doc);
    })
    
    1. modelcreate()方法
      使用save()方法,需要先实例化为entity,再使用save()方法保存文档document。而create()方法直接在模型model上操作,并且可以同时新增多个文档document
      Model.create(doc(s), [callback])
    mongoose.connect('mongodb://test:test@127.0.0.1:27017/test');
    
    var schema = new mongoose.Schema({name: String, age: Number});
    var model = mongoose.model('temp', schema);
    model.create([{name:"lily"}, {name:'jane'}],(err, docs) => {
        console.log(docs);
        //[ { _id: 5b643cc96ca76572d64e242c, name: 'lily', __v: 0 },
        //{ _id: 5b643cc96ca76572d64e242d, name: 'jane', __v: 0 } ]
    });
    
    1. modelinsertMany()方法
    mongoose.connect('mongodb://test:test@127.0.0.1:27017/test');
    var schema = new mongoose.Schema({name: String, age: Number});
    var model = mongoose.model('temp', schema);
    model.insertMany([{name:"a"}, {name:"b"}],(err, docs) => {
      console.log(docs);
      //[ { _id: 5b643db39d69e1731042a0f1, name: 'a', __v: 0 },
      // { _id: 5b643db39d69e1731042a0f2, name: 'b', __v: 0 } ]
    });
    

    注意:新增文档方法的callback回调函数不能使用exec方法改写。查询文档、更新文档以及删除文档方法的callback回调函数大多数都可以使用exec方法改写。
    问题:modelcreate()方法与insertMany()方法的区别。

    七. 查询文档

    1. mongoose提供了三种查询文档document的方法:
      (1) find()
      (2) findById()
      (3) findOne()
    2. find()方法
      Model.find(conditions, [projection], [options], [callback])
    mongoose.connect('mongodb://test:test@127.0.0.1:27017/test');
    
    var schema = new mongoose.Schema({name: String, age: Number});
    var model = mongoose.model('temp', schema);
    for(let i = 1; i < 10; i++) {
      new model({name: `jake${i}`, age: i}).save();
    }
    
    //查找所有文档
    model.find((err, docs) => {
      console.log(docs);//1,2,3,4,5,6,7,8,9
    });
    
    //查找年龄大于等于6的文档
    model.find({age: {$gte: 6}}, (err, docs) => {
      console.log(docs); //6,7,8,9
    });
    
    //查找年龄大于等于6文档的另一种写法
    model.find({age: {$gte: 6}}).exec((err, docs) => {
      console.log(docs); //6,7,8,9
    });
    
    //年龄大于8,且名字存在'jake'的数据
    model.find({age: {$gt: 8}, name: /jake/}, (err, docs) => {
      console.log(docs); //9
    });
    
    //年龄等于1,且只输出'name'字段
    model.find({age: {$lt: 3}}, 'name', (err, docs) => {
      console.log(docs);
      //[ { _id: 5b644b5a43048277c40834c6, name: 'jake1' },
      //{ _id: 5b644b5a43048277c40834c7, name: 'jake2' } ]
    });
    
    //年龄等于1,且不需要输出_id
    model.find({age: 1}, {name: 1, _id: 0}, (err, docs) => {
      console.log(docs); //[ { name: 'jake1' } ]
    });
    
    //搜索年龄大于2,且搜索结果跳过前2条, 只留3条,且按年龄倒序
    model.find({age: {$gt: 2}}).skip(2).limit(3).sort({age: -1}).exec((err, docs) => {
      console.log(docs); //7,6,5
    });
    

    注释: 可参考mongo.exe程序以及node原生查询mongodb数据库API,接口类似。
    注意:① 两种写法find(..., callback)find(...).exec(callback)。② node查询通过toArray(err, docs)方法获取文档数组,mongoose查询通过exec(err, docs)方法获取文档数组。

    1. findById()方法
      Model.findById(id, [projection], [options], [callback])
    model.findById('5b644b5a43048277c40834c7', (err, doc) => {
      console.log(doc);
      //{ _id: 5b644b5a43048277c40834c7, name: 'jake2', age: 2, __v: 0 }
    });
    
    //另一种写法
    model.findById('5b644b5a43048277c40834c7').exec((err, doc) => {
      console.log(doc);
      //{ _id: 5b644b5a43048277c40834c7, name: 'jake2', age: 2, __v: 0 }
    });
    
    //只输出name字段
    model.findById('5b644b5a43048277c40834c7', {name: 1, _id: 0}).exec((err, doc) => {
      console.log(doc);
      //{ name: 'jake2' }
    });
    
    //输出最少字段
    model.findById('5b644b5a43048277c40834c7', {lean: true}).exec((err, doc) => {
      console.log(doc);
      //{ _id: 5b644b5a43048277c40834c7 }
    });
    
    1. findOne()方法
      该方法返回查找到的所有实例的第一个。
      Model.findOne([conditions], [projection], [options], [callback])
    model.findOne({age: {$gt: 5}}, (err, doc) => {
      console.log(doc);
      //{ _id: 5b644b5a43048277c40834cb, name: 'jake6', age: 6, __v: 0 }
    });
    
    model.findOne({age: {$gt: 5}}, {_id: 0}).exec((err, doc) => {
      console.log(doc);
      //{ name: 'jake6', age: 6, __v: 0 }
    });
    
    model.findOne({age: {$gt: 5}}, {name: 1}).lean().exec((err, doc)=> {
      console.log(doc);
      //{ _id: 5b644b5a43048277c40834cb, name: 'jake6' }
    });
    
    1. 常用的查询条件如下
    $or         或关系
    $nor        或关系取反
    $gt        大于
    $gte        大于等于
    $lt        小于
    $lte        小于等于
    $ne          不等于
    $in          在多个值范围内
    $nin         不在多个值范围内
    $all        匹配数组中多个值
    $regex      正则,用于模糊查询
    $size      匹配数组大小
    $maxDistance 范围查询,距离(基于LBS)
    $mod      取模运算
    $near      邻域查询,查询附近的位置(基于LBS)
    $exists     字段是否存在
    $elemMatch   匹配内数组内的元素
    $within    范围查询(基于LBS)
    $box       范围查询,矩形范围(基于LBS)
    $center     范围醒询,圆形范围(基于LBS)
    $centerSphere 范围查询,球形范围(基于LBS)
    $slice      查询字段集合中的元素(比如从第几个之后,第N到第M个元素)
    
    1. $where操作符
      如果要进行更复杂的查询,需要使用$where操作符,$where操作符功能强大而且灵活,它可以使用任意的JavaScript作为查询的一部分,包含JavaScript表达式的字符串或者JavaScript函数。
      (1) 使用字符串
    model.find({$where:"this.x == this.y"},(err,docs) => {
      console.log(docs);
      //[{ _id: 5972ed35e6f98ec60e3dc887,name: 'wang',age: 18,x: 1,y: 1},
      //{ _id: 5972ed35e6f98ec60e3dc889, name: 'li', age: 20, x: 2, y: 2 }]
    });
    
    model.find({$where:"obj.x == obj.y"}, (err,docs) =>{
      //[ { _id: 5972ed35e6f98ec60e3dc887,name: 'wang',age: 18,x: 1,y: 1},
      //{ _id: 5972ed35e6f98ec60e3dc889, name: 'li', age: 20, x: 2, y: 2 }]
      console.log(docs);
    });
    

    (2) 使用函数

    model.find({$where:() => {
            return obj.x !== obj.y;
        }}, (err,docs) =>{
        //[ { _id: 5972ed35e6f98ec60e3dc886,name: 'huochai',age: 27,x: 1,y: 2 },
        //{ _id: 5972ed35e6f98ec60e3dc888, name: 'huo', age: 30, x: 2, y: 1 } ]
        console.log(docs);
    }) 
    
    model.find({$where:() => {
            return this.x !== this.y;
        }},(err,docs) =>{
        //[ { _id: 5972ed35e6f98ec60e3dc886,name: 'huochai',age: 27,x: 1,y: 2 },
        //{ _id: 5972ed35e6f98ec60e3dc888, name: 'huo', age: 30, x: 2, y: 1 } ]
        console.log(docs);
    }) 
    

    八. 更新文档

    更新方法

    1. 文档更新可以使用以下几种方法。
      (1) update()
      (2) updateOne()
      (3) updateMany()
      (4) find() + save()
      (5) findOne() + save()
      (6) findByIdAndUpdate()
      (7) fingOneAndUpdate()
    2. update()
      第一个参数conditions为查询条件,第二个参数doc为需要修改的数据,第三个参数options为控制选项,第四个参数是回调函数。
      Model.update(conditions, doc, [options], [callback])
      options有如下选项:
      safe (boolean): 默认为true。安全模式。
      upsert (boolean): 默认为false。如果不存在则创建新记录。
      multi (boolean): 默认为false。是否更新多个查询记录。
      runValidators: 如果值为true,执行Validation验证。
      setDefaultsOnInsert: 如果upsert选项为true,在新建时插入文档定义的默认值。
      strict (boolean): 以strict模式进行更新。
      overwrite (boolean): 默认为false。禁用update-only模式,允许覆盖记录。
    

    (1) 只更新第一条满足条件的数据

    model.update({age: {$gt: 7}}, {age: 10}, (err, row)=> {
      //{ n: 1, nModified: 1, ok: 1 }
      console.log(row);
    });
    
    //使用exec的写法
    model.update({age: {$gt: 7}}, {age: 10}).exec((err, row)=> {
      //{ n: 1, nModified: 1, ok: 1 }
      console.log(row);
    });
    

    (2) 更新所有满足条件的数据

    model.update({age: {$gt: 7}}, {age: 10}, {multi: true}).exec((err, row)=> {
      //{ n: 2, nModified: 1, ok: 1 }
      console.log(row);
    });
    

    (3) 如果没有符合条件的数据,则什么都不做

    model.update({age: 100}, {age: 1000}).exec((err, row)=> {
      //{ n: 0, nModified: 0, ok: 1 }
      console.log(row);
    });
    

    (4) 如果设置upsert参数为true,若没有符合查询条件的文档,mongo将会综合第一第二个参数向集合插入一个新的文档。

    model.update({age: 100}, {name: 'jake100'}, {upsert: true}).exec((err, row)=> {
      //{n: 1,
      // nModified: 0,
      // upserted: [ { index: 0, _id: 5b646510d22cf9feac0bd2f5 } ],
      // ok: 1 }
      console.log(row);
    });
    
    //验证插入文档
    model.find({age: '100'}).exec((err, docs) =>{
      console.log(docs)
      //[ { _id: 5b646510d22cf9feac0bd2f5,
      //     age: 100,
      //     __v: 0,
      //     name: 'jake100' } ]
    });
    

    注意:update()方法中的回调函数不能省略,否则数据不会被更新。如果无需在回调函数中做进一步操作,则可以使用exec()简化代码。
    例如:temp.update({name:/aa/},{age: 0},{upsert:true}).exec();

    1. updateOne()
      updateOne()方法只能更新找到的第一条数据,即使设置{multi:true}也无法同时更新多个文档。
    model.updateOne({age: {$gt: 8}}, {name: 'jake80'}).exec((err, res)=> {
      //{ n: 1, nModified: 1, ok: 1 }
      console.log(res);
    });
    
    1. updateMany()
      updateMany()update()方法唯一的区别就是默认更新多个文档,即使设置{multi:false}也无法只更新第一个文档。
      Model.updateMany(conditions, doc, [options], [callback])
    model.updateMany({age: {$gt: 8}}, {name: 'jake80'}).exec((err, res)=> {
      //{ n: 3, nModified: 3, ok: 1 }
      console.log(res);
    });
    
    1. find() + save()
      如果需要更新的操作比较复杂,可以使用find()+save()方法来处理。
    model.find({age: {$gt: 8}}).exec((err, docs)=> {
      docs.forEach(doc => {
        doc.name = `jake${doc.age}`;
        doc.save();
      });
      console.log(docs);
      //[ { _id: 5b644b5a43048277c40834cd, name: 'jake10', age: 10, __v: 0 },
      //{ _id: 5b644b5a43048277c40834ce, name: 'jake10', age: 10, __v: 0 },
      //{ _id: 5b646510d22cf9feac0bd2f5, age: 100, __v: 0, name: 'jake100' } ]
    });
    
    1. findOne() + save()
      如果需要更新的操作比较复杂,可以使用findOne()+save()方法来处理。
    model.findOne({age: 10}).exec((err, doc)=> {
      doc.name = 'jake9';
      doc.age = 9;
      doc.save();
      console.log(doc);
      //{ _id: 5b644b5a43048277c40834cd, name: 'jake9', age: 9, __v: 0 }
    });
    
    1. findByIdAndUpdate()
      Model.findOneAndUpdate([conditions], [update], [options], [callback])
    2. fingOneAndUpdate()
      Model.findOneAndUpdate([conditions], [update], [options], [callback])

    修改器

    数据准备工作,创建集合及文档数据如下:

    var mongoose =  require('mongoose');
    mongoose.connect('mongodb://test:test@127.0.0.1:27017/test');
    
    var schema = new mongoose.Schema({
      name: String,
      age: Number,
      array: [Number]
    });
    var Model = mongoose.model('temp', schema);
    for(let i = 1; i < 10; i++) {
      new Model({name: `jake${i}`, age: i, array: []
     }).save();
    }
    

    对象修改器

    1. $inc 增减修改器,只对数字有效。
      找到age等于1的文档,修改age字段值,自减5。
    Model.update({age: 1}, {$inc: {age: -5}}).exec()
    
    1. $set 指定一个键的值
    Model.update({name: 'jake1'}, {$set: {age: 2}}).exec()
    
    1. $unset删除一个键
    Model.update({name: 'jake1'}, {$unset: {age: ''}}).exec()
    

    注意:$unset操作符只匹配keyvalue可以是任意值。

    数组修改器

    1. $push数组尾部插入。
      给匹配文档的array键对应数组插入数字1。
    Model.update({age: 2}, {$push: {array: 1}}).exec();
    
    1. $addToSet数组尾部插入,如果存在则不插入。
    Model.update({age: 2}, {$addToSet: {array: 1}}).exec();
    
    1. $pop 数组尾部删除。
      传入1删除数组尾元素,传入-1删除数组首元素。
    Model.update({age: 2}, {$pop: {array: 1}}).exec();
    
    1. $pull删除数组指定元素。
    Model.update({age: 2}, {$pop: {array: 6}}).exec();
    

    九. 删除文档

    1. 有三种方法用于文档删除。
      (1) remove()
      (2) findOneAndRemove()
      (3) findByIdAndRemove()
      注意:这些方法中的回调函数不能省略,否则数据不会被删除。当然,可以使用exec()方法来简写代码。
    2. remove()
      remove有两种形式,一种是Modelremove()方法,一种是documentremove()方法。
      (1) Modelremove()方法
      该方法的第一个参数conditions为查询条件,第二个参数为回调函数。
      model.remove(conditions, [callback])
    model.remove({age: {$gte:9}}, (err, res) => {
      console.log(res);
      //{ n: 3, ok: 1 }
    });
    
    //使用exec的写法
    model.remove({age: {$gte:9}}).exec((err, res) => {
      console.log(res);
      //{ n: 0, ok: 1 }
    });
    

    (2) documentremove()方法
    document.remove([callback])

    model.findOne({age: 8}).exec((err, doc) => {
        doc.remove((err, doc) => {
          console.log(doc);
          //{ _id: 5b64fdfa1bfab0852697bc00, name: 'jake8', age: 8, __v: 0 }
        });
    });
    

    注释:①modelremove()方法回调可以使用exec()方法改写, documentremove()方法不可以。②modelremove()方法删除符合条件的所有document文档,documentremove()方法删除当前文档。

    1. findOneAndRemove()
      modelremove()会删除符合条件的所有数据,如果只删除符合条件的第一条数据,则可以使用modelfindOneAndRemove()方法。
      Model.findOneAndRemove(conditions, [options], [callback])
    model.findOneAndRemove({age: {$gte: 0}}, (err, doc) => {
      console.log(doc);
      //{ _id: 5b644b5a43048277c40834c6, name: 'jake1', age: 1, __v: 0 }
    });
    
    model.findOneAndRemove({age: {$gte: 0}}).exec((err, doc) => {
      console.log(doc);
      //{ _id: 5b644b5a43048277c40834c7, name: 'jake2', age: 2, __v: 0 }
    });
    
    1. findByIdAndRemove()
      Model.findByIdAndRemove(id, [options], [callback])
    model.find().exec((err, docs) => {
      const docIdArr = docs.map(doc => doc._id);
      model.findByIdAndRemove(docIdArr[0]).exec((err, doc) => {
        console.log(doc);
        //{ _id: 5b644b5a43048277c40834c8, name: 'jake3', age: 3, __v: 0 }
      })
    });
    

    十. Promise

    1. Mongoose异步操作,例如.save()方法,会返回一个ES6标准的promises。你可以使用类似 MyModel.findOne({}).then()await MyModel.findOne({}).exec()的写法。
    var gnr = new Band({
      name: "Guns N' Roses",
      members: ['Axl', 'Slash']
    });
    
    var promise = gnr.save();
    assert.ok(promise instanceof Promise);
    
    promise.then(function (doc) {
      assert.equal(doc.name, "Guns N' Roses");
    });
    
    1. mongoose queries 查询操作虽然有then方法,但并不是一个完全的promise。可以使用exec()方法将其转化为一个完全的promise
    var query = Band.findOne({name: "Guns N' Roses"});
    assert.ok(!(query instanceof Promise));
    
    // A query is not a fully-fledged promise, but it does have a `.then()`.
    query.then(function (doc) {
      // use doc
    });
    
    // `.exec()` gives you a fully-fledged promise
    var promise = query.exec();
    assert.ok(promise instanceof Promise);
    
    promise.then(function (doc) {
      // use doc
    });
    
    1. 可以通过重写mongoose.Promise的方式使用第三方promise库,例如 bluebird
    var query = Band.findOne({name: "Guns N' Roses"});
    
    // Use bluebird
    mongoose.Promise = require('bluebird');
    assert.equal(query.exec().constructor, require('bluebird'));
    

    十一. 前后钩子

    1. 前后钩子即pre()post()方法,又称为中间件,是在执行某些操作时可以执行的函数。中间件在schema上指定,类似于静态方法或实例方法等。
      注意:①前后钩子方法定义在schema上。② 前后钩子方法必须在Model创建之前定义,否则不生效。
    2. 可以在model执行下列操作时,设置前后钩子。
      init validate save remove count find findOne findOneAndRemove findOneAndUpdate insertMany update
    3. pre()中间件
      find()方法为例,在执行find()方法之前,执行pre()方法。
    mongoose.connect('mongodb://test:test@127.0.0.1:27017/test');
    
    var schema = new mongoose.Schema({name: String, age: Number});
    
    schema.pre('find', (next) => {
      console.log('pre hook fu1');
      next();
    });
    
    schema.pre('find', (next) => {
      console.log('pre hook fu2');
      next();
    });
    
    var Model = mongoose.model('temp', schema);
    
    Model.find((err, docs) => {
      console.log(docs[0]);
      //pre hook fu1
      //pre hook fu2
      //{ _id: 5b644b5a43048277c40834c9, name: 'jake4', age: 4, __v: 0 }
    });
    
    1. post()中间件
      post()方法并不是在执行某些操作后再去执行的方法,而在执行某些操作前最后执行的方法,post()方法里不可以使用next()
    mongoose.connect('mongodb://test:test@127.0.0.1:27017/test');
    
    var schema = new mongoose.Schema({name: String, age: Number});
    
    schema.post('find', (next) => {
      console.log('post hook fu1');
    });
    
    schema.post('find', (next) => {
      console.log('post hook fu2');
    });
    
    var Model = mongoose.model('temp', schema);
    
    Model.find((err, docs) => {
      console.log(docs[0]);
      //post hook fu1
      //post hook fu2
      //{ _id: 5b644b5a43048277c40834c9, name: 'jake4', age: 4, __v: 0 }
    });
    

    十二. 查询后处理

    1. 常用的查询后处理的方法如下所示
    • sort 排序
    • skip 跳过
    • limit 限制
    • select 显示字段
    • exect 执行
    • count 计数
    • distinct 去重
    1. 方法示例
    mongoose.connect('mongodb://test:test@127.0.0.1:27017/test');
    
    var schema = new mongoose.Schema({name: String, age: Number});
    var Model = mongoose.model('temp', schema);
    
    for(let i = 1; i <= 15; i++) {
      new Model({name: `jake${i}`, age: i}).save();
    }
    
    Model.find((err, docs) => {
      console.log(docs); //1 - 15
    });
    
    //sort 排序
    Model.find().sort({'age': -1}).exec((err, docs) => {
      console.log(docs); //15 - 1
    });
    
    //skip 跳过
    Model.find().skip(3).exec((err, docs) => {
      console.log(docs); //4 - 15
    });
    
    //limit 限制
    Model.find().limit(2).exec((err, docs) => {
      console.log(docs); //1 - 2
    });
    
    //select 限制字段
    Model.find().select({name: 1, _id: 0}).exec((err, docs) => {
      console.log(docs[0]); //{ name: 'jake2' }
    });
    
    //链式操作
    Model.find().sort({'age': -1}).skip(2).limit(1).select({age: 1, _id: 0}).exec((err, docs) => {
      console.log(docs); //[ { age: 13 } ]
    });
    
    //count显示文档数目
    Model.find().count().exec((err, count) => {
      console.log(count); //15
    });
    
    //distinct 去重
    Model.find().distinct('name').exec((err, arr) => {
      console.log(arr); //jake1-jake15
    });
    

    十三. 文档验证

    1. 如果不进行文档验证,保存文档时,就可以不按照Schema设置的字段进行设置,分为以下几种情况。
    var mongoose =  require('mongoose');
    mongoose.connect('mongodb://test:test@127.0.0.1:27017/test');
    
    var schema = new mongoose.Schema({name: String, age: Number,x: Number, y: Number});
    var Model = mongoose.model('temp', schema);
    

    (1) 缺少字段的文档也可以保存成功

    new Model({age: 10}).save((err, doc) => {
      console.log(doc);
      //{ _id: 5b65862905b74ea05e211322, age: 10, __v: 0 }
    });
    

    (2) 包含未设置的字段的文档也可以保存成功,未设置的字段不被保存。

    new Model({age: 11, z: 10}).save((err, doc) => {
      console.log(doc);
      //{ _id: 5b65865ca0864ba06fb02d37, age: 11, __v: 0 }
    });
    

    (3) 包含字段类型与设置不同的文档可以保存成功,不同字段类型的字段被保存为设置的字段类型

    new Model({age:true,name:10}).save(function(err,doc){
      //{ _id: 5b6586b547589ba082d19c3c, age: 1, name: '10', __v: 0 }
      console.log(doc);
    });
    
    1. 通过文档验证,就可以避免以上几种情况发生。文档验证在SchemaType中定义,格式如下。
      {name: {type:String, validator:value}}
      常用验证包括以下几种:
      required: 数据必须填写
      default: 默认值
      min: 最小值(只适用于数字)
      max: 最大值(只适用于数字)
      match: 正则匹配(只适用于字符串)
      enum: 枚举匹配(只适用于字符串)
      validate: 自定义匹配
    2. required文档验证
      age设置为必填字段,如果没有age字段,文档将不被保存,且出现错误提示。
    var schema = new mongoose.Schema({age:{type:Number,required:true}, name: String,x:Number,y:Number});
    var Model = mongoose.model('temp', schema);
    new Model({name:"abc"}).save((err,doc) => {
      //Path `age` is required.
      console.log(err.errors['age'].message);
    });
    
    1. default文档验证
      设置age字段的默认值为18,如果不设置age字段,则会取默认值。
    var schema = new mongoose.Schema({ age:{type:Number,default:18}, name:String,x:Number,y:Number});
    var Model = mongoose.model('temp', schema);
    new Model({name:'a'}).save((err,doc) => {
      //{ __v: 0, name: 'a', _id: 59730d2e7a751d81582210c1, age: 18 }
      console.log(doc);
    });
    
    1. min、max文档验证
      age的取值范围设置为[0,10]。如果age取值为20,文档将不被保存,且出现错误提示。
    var schema = new mongoose.Schema({ age:{type:Number,min:0,max:10}, name: String,x:Number,y:Number});
    var Model = mongoose.model('temp', schema);
    new Model({age:20}).save((err,doc) => {
      //Path `age` (20) is more than maximum allowed value (10).
      console.log(err.errors['age'].message);
    });
    
    1. match文档验证
      namematch设置为必须存在'a'字符。如果name不存在'a',文档将不被保存,且出现错误提示。
    var schema = new mongoose.Schema({ age:Number, name:{type:String,match:/a/},x:Number,y:Number});
    var Model = mongoose.model('temp', schema);
    new Model({name:'bbb'}).save((err,doc) => {
      //Path `name` is invalid (bbb).
      console.log(err.errors['name'].message);
    });
    
    1. enum文档验证
      name的枚举取值设置为['a','b','c'],如果name不在枚举范围内取值,文档将不被保存,且出现错误提示。
    var schema = new mongoose.Schema({ age:Number, name:{type:String,enum:['a','b','c']},x:Number,y:Number});
    var Model = mongoose.model('temp', schema);
    new Model({name:'bbb'}).save((err,doc) => {
      //`bbb` is not a valid enum value for path `name`.
      console.log(err.errors['name'].message)
    });
    
    1. validate文档验证
      validate实际上是一个函数,函数的参数代表当前字段的值,返回true表示通过验证,返回false表示未通过验证。利用validate可以自定义任何条件。
      例如,定义名字name的长度必须在4个字符以上。
    var schema = new mongoose.Schema({ 
      name:{
        type: String, 
        validate: value =>value.length > 4
      }, 
      age: Number,
      x: Number,
      y: Number
    });
    var Model = mongoose.model('temp', schema);
    new Model({name:'abc'}).save((err, doc) => {
      //Validator failed for path `name` with value `abc`
      console.log(err.errors['name'].message);
    });
    

    十四. population连表操作

    1. population介绍
      (1) MongoDB是文档型数据库,所以它没有关系型数据库joins(数据库的两张表通过"外键"建立连接关系) 特性。在建立数据的关联时会比较麻烦。为了解决这个问题,Mongoose封装了一个population功能。使用population可以实现在一个 document中填充其他 collection(s)document(s)
      (2) 在定义schema的时候,如果设置某个 field 关联另一个schema,那么在获取 document 的时候就可以使用 population 功能通过关联schemafield 找到关联的另一个 document,并且用被关联 document 的内容替换掉原来关联字段(field)的内容。
    2. 连表关系场景
      场景:用户user可以写文章post,并且对文章post进行评论comment
      分析:一个用户user可以写多篇文章post。一篇文章post只能有一个作者user,但可以有多条评论comment。一条评论comment 只属于一篇文章post,且只属于一个用户user
      示例:用户A写了文章A、评论A,用户B写了文章B、评论B,用户C写了文章C、评论C;用户A在文章B上添加了评论A,用户B在文章C上添加了评论B,用户C在文章A上添加了评论C
    3. 连表关系示例代码
      (1) 创建用户、文章、评论三个schemaModel
    var mongoose =  require('mongoose');
    mongoose.connect('mongodb://test:test@127.0.0.1:27017/test');
    
    var Schema   = mongoose.Schema;
    
    //创建用户scheme以及model
    var userSchema = new Schema({
      name  : String,
      posts : [{ type: Schema.Types.ObjectId, ref: 'post' }]
    });
    var UserModel = mongoose.model('user', userSchema);
    
    //创建文章scheme以及model
    var postSchema = new Schema({
      name  : String,
      poster   : { type: Schema.Types.ObjectId, ref: 'user' },
      comments : [{ type: Schema.Types.ObjectId, ref: 'comment' }],
    });
    var PostModel = mongoose.model('post', postSchema);
    
    //创建评论scheme以及model
    var commentSchema = new Schema({
      name  : String,
      post      : { type: Schema.Types.ObjectId, ref: "post" },
      commenter : { type: Schema.Types.ObjectId, ref: 'user' },
    });
    var CommentModel = mongoose.model('comment', commentSchema);
    
    • 创建了三个 ModelUserModelPostModelCommentModel
    • UserModel 的属性 posts对应是一个 ObjectId 的数组,ref表示关联PostModel
    • PostModel的属性 postercomments 分别关联UserModelCommentModel
    • CommentModel的属性 postcommenter 分别关联PostModelUserModel
      注意:ref指向mongoose.model(name, schema);方法的name参数,而不是方法返回值model
      (2) 创建entity实例、建立关系并保存数据
    //创建三个用户userA、userB、userC
    var userA = new UserModel({name: 'userA'});
    var userB = new UserModel({name: 'userB'});
    var userC = new UserModel({name: 'userC'});
    //创建三篇文章postA、postB、postC
    var postA = new PostModel({name:  'postA'});
    var postB = new PostModel({name:  'postB'});
    var postC = new PostModel({name:  'postC'});
    //创建三个评论commentA、commentB、commentC
    var commentA = new CommentModel({name: 'commentA'});
    var commentB = new CommentModel({name: 'commentB'});
    var commentC = new CommentModel({name: 'commentC'});
    
    //建立用户与文章的关系
    userA.posts.push(postA._id);
    //moongoose封装的语法糖,与userA.posts.push(postA)写法含义相同
    userB.posts.push(postB);
    userC.posts.push(postC);
    //建立文章与用户、评论的关系
    postA.poster = userA;
    postB.poster = userB;
    postC.poster = userC;
    postA.comments.push(commentC);
    postB.comments.push(commentA);
    postC.comments.push(commentB);
    //建立评论与用户、文章的关系
    commentA.post = postB;
    commentB.post = postC;
    commentC.post = postA;
    commentA.commenter = userA;
    commentB.commenter = userB;
    commentC.commenter = userC;
    
    //保存数据
    userA.save();
    userB.save();
    userC.save();
    postA.save();
    postB.save();
    postC.save();
    commentA.save();
    commentB.save();
    commentC.save();
    
    1. population连表操作
      (1) query.populate
      语法:query.populate(path, [select], [model], [match], [options])
      pathString | Object ;指定要填充的关联字段。
      selectObject | String;指定填充 document中的哪些字段。
      modelModel;指定关联字段的model,若未指定则使用Schemaref
      matchObject;指定附加的查询条件。
      optionsObject;指定附加的其他查询选项,如排序以及条数限制等。
    UserModel.find().skip(1).limit(1)
      .populate('posts', 'name')
      .exec((err, docs) => {
        console.log(docs[0].posts);
        //[{"_id":"5b6655ac4f1303b2933a7dc0","name":"postB"}]
    });
    
    UserModel.findOne({name: 'userC'})
      .populate({
        path: 'posts',
        select: { name: 1, _id: 0}
      })
      .exec((err, doc) => {
        console.log(doc.posts); //[{"name":"postC"}]
      });
    
    PostModel.findOne({name: 'postA'})
      .populate('poster comments', 'name -_id')
      .exec((err, doc)=> {
        console.log(doc.poster); //{ name: 'userA' }
        console.log(doc.comments); //[{"name":"commentC"}]
      });
    
    PostModel.findOne({name: 'postC'})
      .populate([
        {path: 'poster', select: 'name'},
        {path: 'comments', select: {_id: 0}}
      ])
      .exec((err, doc) => {
        console.log(doc.poster);
        //{ _id: 5b6655ac4f1303b2933a7dbe, name: 'userC' }
        console.log(doc.comments);
        //[{"name":"commentB","post":"5b6655ac4f1303b2933a7dc1","commenter":"5b6655ac4f1303b2933a7dbd","__v":0}]
      });
    

    (2) Model.populate
    语法:Model.populate(docs, options, [cb(err,doc)])

    CommentModel.findOne((err, doc) => {
      CommentModel.populate(doc, {path: 'post commenter', select: 'name'}, (err, doc) => {
        console.log(doc.post);
        //{ _id: 5b6655ac4f1303b2933a7dc0, name: 'postB' }
        console.log(doc.commenter);
        //{ _id: 5b6655ac4f1303b2933a7dbc, name: 'userA' }
      })
    });
    

    (3) document.populate
    语法:Document.populate([path], [callback])

    CommentModel.findOne((err, doc) => {
      doc.populate({path: 'post commenter', select: 'name'}, (err, doc) => {
        console.log(doc.post);
        //{ _id: 5b6655ac4f1303b2933a7dc0, name: 'postB' }
        console.log(doc.commenter);
        //{ _id: 5b6655ac4f1303b2933a7dbc, name: 'userA' }
      })
    });
    

    十五. 参考资料

    Mongoose官网
    Mongoose Promise语法
    Mongoose基础入门
    Mongoose 之 Population 使用
    Mongoose 使用之 Population

    相关文章

      网友评论

        本文标题:Mongoose基础入门

        本文链接:https://www.haomeiwen.com/subject/plzevftx.html