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 查询结果文档
});
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使用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 }
])