Mongoose笔记

1,541 阅读8分钟

Mongoose是什么

  • Mongoose是MongoDB的一个对象模型工具
  • 同时它也是针对MongoDB操作的一个对象模型库,封装了MongoDB对文档的一些增删改查等常用方法
  • Mongoose因为封装了MongoDB对文档操作的常用方法,可以高效处理mongodb,还提供了类似Schema的功能,如hook、plugin、virtual、populate等机制。

使用mongoose

安装mongoose

cnpm i mongoose -S

使用mongoose

let mongoose = require("mongoose");
let db = mongoose.createConnection("mongodb://user:pass@ip:port/database", { useNewUrlParser: true,useUnifiedTopology: true })
  • user 用户名
  • pass 密码
  • ip IP地址
  • port 端口号
  • database 数据库

使用mongoose

let mongoose = require('mongoose');
let connection = mongoose.createConnection("mongodb://127.0.0.1/zfpx",{ useNewUrlParser: true,useUnifiedTopology: true});
connection.on('error', function (error) {
    console.log('数据库连接失败: ' + error);
});
connection.on('open', function (error) {
    console.log('数据库连接成功');
});

Schema

  • Schema是数据库集合的模型骨架
  • 定义了集合中的字段的名称和类型以及默认值等信息

Schema.Type

  • NodeJS中的基本数据类型都属于Schema.Type
  • 另外Mongoose还定义了自己的类型
  • 基本类型有
    • 字符串(String)
    • 日期型(Date)
    • 数值型(Number)
    • 布尔型(Boolean)
    • null
    • 数组([])
    • 内嵌文档

定义Schema

 var personSchema = new Schema({
      name:String, //姓名
      binary:Buffer,//二进制
      living:Boolean,//是否活着
      birthday:Date,//生日
      age:Number,//年龄
      _id:Schema.Types.ObjectId,  //主键
      _fk:Schema.Types.ObjectId,  //外键
      array:[],//数组
      arrOfString:[String],//字符串数组
      arrOfNumber:[Number],//数字数组
      arrOfDate:[Date],//日期数组
      arrOfBuffer:[Buffer],//Buffer数组
      arrOfBoolean:[Boolean],//布尔值数组
      arrOfObjectId:[Schema.Types.ObjectId]//对象ID数组
      nested:{ name:String} //内嵌文档
    });

    let p = new Person();
    p.name= 'zfpx';
    p.age = 25;
    p.birthday = new Date();
    p.married = false;
    p.mixed= {any:{other:'other'}};
    p._otherId = new mongoose.Types.ObjectId;
    p.hobby.push('smoking');
    p.ofString.push('string');
    p.ofNumber.pop(3);
    p.ofDates.addToSet(new Date);
    p.ofBuffer.pop();
    p.ofMixed = ['anything',3,{name:'zfpx'}];
    p.nested.name = 'zfpx';

Model

Model是由通过Schema构造而成,除了具有Schema定义的数据库骨架之外,还可以操作数据库

var mongoose = require('mongoose');
var connection = mongoose.createConnection("mongodb://127.0.0.1/blog",{ useNewUrlParser: true,useUnifiedTopology: true});
connection.on('error', function(err) {console.log('error')});
let PersonSchema = new mongoose.Schema({
    name: String,
    age: Number
});
// 两个参数表示定义一个模型
var PersonModel = connection.model("Person", PersonSchema);
// 如果该Model已经定义, 则可以直接通过名字获取
var PersonMOdel = connection.model("Person")

Entity简述

  • 通过Model创建的实体,它也可以操作数据库
  • 使用Model创建Entity
let personEntity = new PersonModel({
    name: "jinxin",
    age: 6
})

Schema生成Model,Model创造Entity,Model和Entity都可以对数据库操作,但Model比Entity实现的功能多

保存Entity

let mongoose = require("mongoose");
let conn = mongoose.createConnection("mongodb://127.0.0.1/jinxin",{ useNewUrlParser: true,useUnifiedTopology: true});
let PersonSchema = new mongoose.Schema({
    name: {type: String},
    age: {type: Number, default: 0}
});
let PersonModel = conn.model("Person", PersonSchema);

let PersonEntity = new PersonModel({
    name: "jinxin",
    age: 6
});

PersonEntity.save(function (error, doc) {
    if (error) {
        console.log("error :" + error);
    } else {
       //doc是返回刚存的person对象 
        console.log(doc);
    }
});

ObjectID

  • 存储在mongodb集合中的每个文档都有一个默认的主键_id
  • 这个主键名称是固定的,它可以是mongodb支持的任何数据类型,默认是ObjectId 该类型的值由系统自己生成,从某种意义上几乎不会重复
  • ObjectId使用12字节的存储空间,是一个由24个16进制数字组成的字符串(每个字节可以存储两个16进制数字)
部分 含义
4字节 0 时间戳是自 1970 年 1 月 1 日(08:00:00 GMT)至当前时间的总秒数,它也被称为 Unix 时间戳,单位为秒
3字节 100 所在主机的唯一标识符,通常是机器主机名的散列值(hash),可以确保不同主机生成不同的ObjectId不产生冲突
2字节 1000 产生ObjectId的进程的进程标识符(PID)
4字节 1000 由一个随机数开始的计数器生成的值

基础操作

查询

Model.find(查询条件,callback);

Model.find({},function(error,docs){
  //若没有向find传递参数,默认的是显示所有文档
});

Model.find({ "age": 6 }, function (error, docs) {
  if(error){
    console.log("error :" + error);
  }else{
    console.log(docs); //docs: age为6的所有文档
  }
});

Model保存

Model.create(文档数据, callback))

PersonModel.create({ name:"zfpx", age:7}, function(error,doc){
    if(error) {
        console.log(error);
    } else {
        console.log(doc);
    }
});

Entity保存

Entity.save(callback)

var PersonEntity = new PersonModel({name:"jinxin",age: 9});
PersonEntity.save(function(error,doc) {
   if(error) {
      console.log(error);
   } else {
      console.log(doc);
   }
});

更新

Model.update(查询条件,更新对象,callback)

  var conditions = {name : 'jinxin'};
  var update = {$set : { age : 100 }};
  PersonModel.update(conditions, update, function(error){
      if(error) {
          console.log(error);
      } else {
          console.log('Update success!');
        }
    });

删除

Model.remove(查询条件,callback);

var conditions = {name: 'jinxin'};
PersonModel.remove(conditions, function(err) {
    if(error) {
        console.log(error)
    } else {
        console.log('Delete success')
    }
})

基本操作

准备数据

PersonModel.create([
                          { name:"test1", age:1 },
                          { name:"test2", age:2 },
                          { name:"test3", age:3 },
                          { name:"test4", age:4 },
                          { name:"test5", age:5 },
                          { name:"test6", age:6},
                          { name:"test7", age:7 },
                          { name:"test8", age:8 },
                          { name:"test9", age:9},
                          { name:"test10",age:10 }
                         ], function(error,docs) {
            if(error) {
                console.log(error);
            } else {
                console.log('save ok');
            }
        });

属性过滤

Model.find(Conditions, fields, callback)

//field省略或为Null,则返回所有属性。
//返回只包含name、age两个键的所有记录
Model.find({},{name:1, age:1, _id:0},function(err,docs){
   //docs 查询结果集
})

只需要把显示的属性设置为大于零的书就可以

findOne(查询单条)

与find相同,当只返回单个文档 findOne(Conditions, callback)

TestModel.findOne({ age: 6}, function (err, doc){
       // 查询符合age等于6的第一条数据
       // doc是查询结果
})

findById(按照ID查找)

findById(_id, callback)

PersonModel.findById(person._id, function (err, doc){
     //doc 查询结果文档
});

gt,lt

Model.find({"age":{"$gt":6}},function(error,docs){
   //查询所有nage大于6的数据
});

Model.find({"age":{"$lt":6}},function(error,docs){
   //查询所有nage小于6的数据
});

Model.find({"age":{"$gt":6,"$lt":9}},function(error,docs){
  //查询所有nage大于6小于9的数据
});

$ne

Model.find({ age:{ $ne:6}},function(error,docs){
  //查询age不等于6的所有数据
});

$in

Model.find({ age:{ $in: 6}},function(error,docs){
   //查询age等于6的所有数据
});

Model.find({ age:{$in:[6,7]}},function(error,docs){
  //可以把多个值组织成一个数组
});

$or

Model.find({"$or":[{"name":"jinxin"},{"age":6}]},function(error,docs){
    //查询name为jinxin或age为6的全部文档
});

$exists(是否存在)

$exists操作符,可用于判断某些关键字段是否存在来进行条件查询,如下:

Model.find({name:{$exists:true}}, function(error,docs) {
    // 用来查询所有存在name属性的文档
})
Model.find({name:{$exists:false}}, function(error,docs) {
    // 用来查询所有不存在name属性的文档
})

高级查询

可以限制结果的数量,跳过部分结果,根据任意键对结果进行各种排序

所有这些选项都要在查询被发送到服务器之前

limit(限制数量)

在查询操作中,有时数据量会很大,这时我们就需要对返回结果的数量进行限制,那么我们就可以使用limit函数,通过它来限制结果数量

find(Conditions, fields, options, callback);

Model.find({}, null, {limit: 20}, function(err, docs) {
    console.log(docs);
})

skip(跳过、略过的数量)

skip函数的功能是略过指定的数量的匹配结果,返回余下的查询结果 find(Conditions, fields, options, callbacks);

Model.find({}, null, {skip: 4}, function(err ,docs){
    console.log(docs)
}}

sort函数

sort函数可以将查询结果进行排序操作,该函数的参数是一个或多个键值对, 键代表要排序的键名,值代表排序的方向,1升序,-1降序 find(Conditions, fields, options, callback)

Model.find({}, null, {sort:{age: -1}}, function(err, docs) {
    
})

分页查询

Model('User').find()
    .sort({createAt: -1})
    .skip((pageNum - 1) * pageSize)
    .limit(pageSize)
    .populate('user')
    .exec(function(err, docs) {
        console.log(docs)
    })

populate

var mongoose = require('mongoose');
// 连接数据库
mongoose.connect('mongodb://localhost:27017/blog');
// 定义课程Schema
var CourseSchema = new mongoose.Schema({
    name: String
})
var CourseModel = new mongoose.model('Course', CourseSchema);
var PersonSchema = new mongoose.Schema({
    name: {
        type: String,
        required: true
    },
    // 外键 别的集合的主键
    course: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'Course' // 指向该集合的外键
    }
});
var PersonModel = new mongoose.model('Person', PersonSchema);
CourseModel.create({name: 'node.js'}, function(err, course) {
    Person.create({name: 'jinxin', course: course: course._id}, function(err, doc) {
        console.log(doc)
    })
})
// mongoose 是一个对象模型工具可就是把对数据的操作映射为对对象的操作
var mongoose = require("mongoose");
// 1、连接数据库
let conn = mongoose.createConnection('mongodb://localhost/school', {
    useNewUrlParser: true,
    useUnifiedTopology: true
})

const {ObjectId} = mongoose.Schema.Types;

// 1、先定义Schema
let Student = conn.model('Student', new mongoose.Schema({
    name: String
}));

let Score = conn.model('Score', new mongoose.Schema({
    stuid: {
        type: ObjectId,  // 对象id的类型
        ref: 'Student', // 是个外键,引用哪个集合的主键
    },
    grade: Number
}))

// Student.create({name:'jinxin'}).then(function(student) {
//     console.log(student);
//     return Score.create({
//         stuid: student._id,
//         grade: 100
//     })
// }).then(score => {
//     console.log(score);
// })
// 5e6f4075ea4266495562af20 对应的学生的名字和分数
// Score.findById('5e6f4075ea4266495562af20', function(err, score) {
//     let { stuid, grade } = score;
//     Student.findById(stuid, function(err, student) {
//         let {name} = student;
//         console.log(`${name}:${grade}`)
//     })
// })

// populate 填充的意思, 把集合文档中的一个外键转换成对应的文档
Score.findById('5e6f4075ea4266495562af20').populate('stuid').exec(function(err, doc) {
    console.log(doc);
    console.log(`${doc.stuid.name}:${doc.grade}`);
})

扩展Mongoose模型

statics对类进行扩展

PersonSchma.statics.findByUsername = function(username, callback) {
    return this.findOne({ username }, callback);
}
Person.findByUsername('jinxin', function(err, doc) {
    console.log(doc)
})

methods对实例进行扩展

PersonSchema.methods.exist = function(callback) {
    let query = {username: this.username, password: this.password};
    return this.model('Person').findOne(query, callback);
}
let person = new Person({ username: 'jinxin', password: '123456' })
person.exist(function(err, doc) {
    
}

virtual虚拟属性

  • vitual是虚拟属性的意思,即原来Schema定义里不存在该属性, 后来通过virtual赋予的属性
  • Schema中定义的属性是要保存到数据库里的,而virtual属性基于已有属性做的二次定义 【 模型属性 = Schema定义的属性+virtual属性 】
PersonSchema.virtual('area').get(function () {
    //this指向实例
    return this.phone.split('-')[0];
});
PersonSchema.virtual('number').get(function () {
    return this.phone.split('-')[1];
});
let Person = conn.model('Person', PersonSchema);
let person = new Person({ username: 'zfpx', password: '123456', phone: '010-6255889', firstname: 'first', lastname: 'last' });
console.log(person.fullname, person.area, person.number);

hook

在用户注册保存的时候,需要先把密码通过salt生成hash密码,并再次赋予给password

PersonSchema.pre('save', function (next) {
    this.password = crypto.createHmac('sha256', 'zfpx').update(this.password).digest('hex');
    next();
});

PersonSchema.statics.login = function (username, password, callback) {
    password = crypto.createHmac('sha256', 'zfpx').update(password).digest('hex');
    return this.findOne({ username, password }, callback);
}

Person.login('zfpx', '123456', function (err, doc) {
    console.log(err, doc);
});

schema插件

Schemas是可插拔的,也就是说,它们提供在应用预先打包能力来扩展他们

module.exports = exports = function lastModified(schema, options) {
    schema.add({lastModify: Date});
    schema.pre('save', function(next) {
        this.lastModify = new Date;
        next();
    });
    if(options && options.index) {
        schema.path('lastModify').index(options.index)
    }
}


let plugin = require('./plugin');
ket Person = new Schema({});
Person.plugin(plugin, {index: true})
  • Person 是用户自己定义的Schema
  • Person.plugin 是为Person增加plugin
  • plugin有2个参数
    • 插件对象 plugin
    • 配置项 {index:true}

schema.add({age:Number});

MongoDB聚合

  • MongoDB中聚合主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果,有点类似于sql语句中的count()
  • MongoDb中的聚合方法是aggregate()

语法

aggregate()方法的基本语法如下 db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)

分组

> db.article.insert({uid:1,content:'1',visit:1});
> db.article.insert({uid:2,content:'2',visit:2});
> db.article.insert({uid:1,content:'3',visit:3});
db.article.aggregate([{$group:{_id:'$uid',total:{$sum:1}}}]);
{ "_id" : 2, "total" : 1 }
{ "_id" : 1, "total" : 2 }

聚合表达式

表达式 描述 实例
$sum 计算总和 db.article.aggregate($group: [_id:"$uid"], num_totorial:{$sum: '$visit'})
$avg 计算平均值 db.article.aggregate([{$group : {_id : "$uid", num_tutorial : {$avg : "$visit"}}}])
$min 获取集合中所有文档对应的最小值 db.article.aggregate([{$group : {_id : "$uid", num_tutorial : {$min : "$visit"}}}])
$max 获取集合中所有文档对应值中的最大值 db.article.aggregate([{$group : {_id : "$uid", num_tutorial : {$max : "$visit"}}}])
$push 把某列的所有值放到一个数组中 db.article.aggregate([{$group : {_id : "$uid", url : {$push: "$url"}}}])
$addToSet 返回一组文档中所有文档所选字段的全部唯一值的数组 db.article.aggregate([{$group : {_id : "$uid", url : {$addToSet : "$url"}}}])
$first 获取资源文档排序中第一个文档数据,可能为null db.article.aggregate([{$group : {_id : "$uid", first_url : {$first : "$url"}}}])
$last 根据资源文档的排序获取最后一个文档的数据,可能为null db.article.aggregate([{$group : {_id : "$uid", last_url : {$last : "$url"}}}])
db.article.insert({uid:1,content:'3',url:'url1'});
db.article.insert({uid:1,content:'4',url:'url1'});
db.article.insert({uid:1,content:'5',url:'url2'});
# 把某列的所有值都放到一个数组中
db.article.aggregate([{$group : {_id : "$uid", url : {$push: "$url"}}}])
{ "_id" : 1, "url" : [ "url1", "url1", "url2"] 

管道的概念

管道在Unix和Linux中一般用于将当前命令的输出结果作为下一个命令的参数,MongoDB的聚合管道将MongoDB文档在一个管道处理完毕后将结果传给下个一管道处理,管道操作是可以重复的

  • $project:修改输入文档的结构。可以用来重命名、增加或删除字段,也可以用于创建计算结果以及嵌套文档。
  • match:用于过滤数据,只输出符合条件的文档。match使用MongoDB的标准查询操作
  • $limit:用来限制MongoDB聚合管道返回的文档数。
  • $skip:在聚合管道中跳过指定数量的文档,并返回余下的文档。
  • $unwind:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。
  • $group:将集合中的文档分组,可用于统计结果。
  • $sort:将输入文档排序后输出。

过滤显示字段

修改输入文档的结构,可以用来重命名、增加或删除字段,也可以用于创建计算结果以及嵌套文档

db.article.aggregate(
  { $project : {
      _id:0,
      content : 1 ,
  }}
);

过滤文档

用于过滤数据,只输出符合条件的文档。$match使用MongoDB的标准查询操作

db.article.aggregate( [
  { $match : { visit : { $gt : 10, $lte : 200 } } },
  { $group: { _id: '$uid', count: { $sum: 1 } } }
]);

跳过指定数量

在聚合管道中跳过指定数量的文档,并返回余下的文档。

js var db = connect('school'); 
var vistors = []; 
for(var i=1;i<=20;i++){ 
    vistors.push({uid:i,visit:i}); 
} 
print(vistors.length);
db.vistors.insert(vistors);
db.vistors.aggregate( [ 
        { $match : { visit : { $gt : 10, $lte : 200 } } }, 
        { $group: { _id: '$uid', count: { $sum: 1 } } }, 
        { $skip : 1 } ] 
);  

$unwind

  • 将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。
  • 使用$unwind可以将weekday中的每个数据都被分解成一个文档,并且除了weekday的值不同外,其他的值都是相同的
db.vistors.aggregate( [
    { $project : {_id:1,uid:1,type:1,visit:1}},
    { $match : { visit : { $gte : 1, $lte : 10 } } },
    { $unwind:'$type'}
]);

$group

将集合中中的文档分组,可用于统计结果

db.vistors.aggregate( [
  { $project : {_id:1,uid:1,type:1,visit:1}},
  { $match : { visit : { $gte : 1, $lte : 10 } } },
  { $unwind:'$type'},
  { $group: { _id: '$uid', count: { $sum: 1 } } },
  { $sort: {_id:1} },
  { $skip : 5 },
  { $limit: 5 }
]);

Mongoose使用

Article.aggregate([
                        { $match : { visit : { $gt : 10, $lte : 200 } } },
                        { $group: { _id: '$uid', count: { $sum: 1 } } },
                        { $skip : 1 }
 ])