ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
[toc] ## 官方文档 > [点击跳转](http://mongoosejs.com/) ## 安装mongoose ``` npm install mongoose ``` ## 利用mongoose连接数据库 - user 用户名 - pass 密码 - ip IP地址 - port 端口号 - database 数据库 格式 ``` var mongoose = require("mongoose"); var db = mongoose.connect("mongodb://user:pass@ip:port/database"); ``` usage ``` let mongoose = require('mongoose'); let conn = mongoose.createConnection('mongodb://localhost/ahhh'); ``` ### 连接事件 ``` //如果说打开数据库失败,那么会触发error事件 conn.on('err',function(err){ console.error('err:',err); }); //如果数据库连接成功,成功的打开了数据库,那么会触发open事件 conn.on('open',function(err){ console.error('open',err); }) ``` ## 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, } }); ``` ``` //第二个参数用来定义集合的名称,不写会生成一个默认的 //注意,这里还没有真正创建集合,而是要在调用modle方法时,并且集合的名字也是在那时决定的 let PersonSchema = new mongoose.Schema({ name:String ,age:Number },{collection:'person'}) ``` - 如果对象中的字段在Schema中没有定义,则会被忽略掉 - 如果说对象中的字段少于Schema中定义的字段,则缺少的字段不会被保存 - 如果提供的字段类型和Schema中定义的不匹配,则会报错 ### Model `Model`是通过`Schema`构造而成,除了具有Schema定义的数据库骨架以外,还可以操作数据库 ``` //连接数据库 mongoose.connect("mongodb://123.57.143.189:27017/ahhh"); //两个参数表示定义一个模型 var PersonModel = mongoose.model("Person", PersonSchema); // 如果该Model已经定义,则可以直接通过名字获取 var PersonModel = mongoose.model('Person');//一个参数表示获取已定义的模型 ``` 拥有了Model,我们也就拥有了操作数据库的能力 >[danger] **注意:** 在数据库中的集合名称等于 模型名转小写再转复数,比如 > > Person>person>people,Child>child>children > Xxx=>xxxs ### Entity实体 通过Model创建的实体,可以操作数据库 ``` var personEntity = new PersonModel({ name : "ahhh", age : 111 }); ``` >[warning] Schema生成Model,Model创造Entity,**Model和Entity都可对数据库操作**,但Model比Entity可以实现的功能更多 ### 保存Entity ``` var mongoose = require("mongoose"); mongoose.connect("mongodb://127.0.0.1:27017/zfpx"); var PersonSchema = new mongoose.Schema({ name: {type: String}, age: {type: Number, default: 0} }); var PersonModel = mongoose.model("Person", PersonSchema); var PersonEntity = new PersonModel({ name: "ahhh", age: 111 }); PersonEntity.save(function (error, doc) { if (error) { console.log("error :" + error); } else { //doc是返回刚存的person对象 console.log(doc); } }); ``` ### ObjectId 存储在mongodb集合中的每个文档都有一个默认的主键_id 这个主键名称是固定的,它可以是mongodb支持的任何数据类型,默认是ObjectId 该类型的值由系统自己生成,从某种意义上几乎不会重复 ObjectId是一个12字节的 BSON 类型字符串。按照字节顺序,依次代表: - 4字节:UNIX时间戳 - 3字节:表示运行MongoDB的机器 - 2字节:表示生成此_id的进程 - 3字节:由一个随机数开始的计数器生成的值 >每一个文档都有一个特殊的键_id,这个键在文档所属的集合中是唯一的。 ## 基础操作 ### 查询 >语法 >Model.find(查询条件,callback); ``` Model.find({},function(error,docs){ //若没有向find传递参数,默认的是显示所有文档 }); Model.find({ "age": 111 }, function (error, docs) { if(error){ console.log("error :" + error); }else{ console.log(docs); //docs: age为111的所有文档 } }); ``` ### Model保存 >语法 >Model.create(查询条件,callback); ``` PersonModel.create({ name:"ahhh", age:111}, function(error,doc){ if(error) { console.log(error); } else { console.log(doc); } }); ``` ### Entity保存 >语法 >Entity.save(callback)) ``` var PersonEntity = new PersonModel({name:"ahhh",age: 111}); PersonEntity.save(function(error,doc) { if(error) { console.log(error); } else { console.log(doc); } }); ``` ### 更新 >语法 >Model.update(查询条件,更新对象,callback); 原生中会整体覆盖,但mongoose中没有这个问题,即使传入了整个文档,也不会直接覆盖原文档,也是按字段覆盖 并且原生中的`$`操作符都能用 ``` var conditions = {name : 'ahhh'}; var update = {$set : { age : 100 }}; PersonModel.update(conditions, update, function(error){ if(error) { console.log(error); } else { console.log('Update success!'); } }); ``` 请注意如果匹配到多条记录,默认只更新一条,如果要更新匹配到的所有记录的话需要(在回调)加一个参数` {multi:true}` ### 删除 >语法 >Model.remove(查询条件,callback); ``` var conditions = { name: 'ahhh' }; PersonModel.remove(conditions, function(error){ if(error) { console.log(error); } else { console.log('Delete success!'); } }); ``` 如果只想删除匹配的第一条的话,需要(在回调之前)添加`{justOne:true}`参数 ### 基本查询 #### 属性过滤 >语法 >find(Conditions,field,callback) ``` //field省略或为Null,则返回所有属性。 //返回只包含name、age两个键的所有记录 Model.find({},{name:1, age:1, _id:0},function(err,docs){ //docs 查询结果集 }) ``` 我们只需要把显示的属性设置为大于零的数就可以,当然1是最好理解的,_id是默认返回,如果不要显示加上`("_id":0)` #### findOne(查询单条) >语法 >findOne(Conditions,callback) find相同,但只返回单个文档,也就说当查询到即一个符合条件的数据时,将停止继续查询,并返回查询结果 语法 ``` TestModel.findOne({ age: 111}, function (err, doc){ // 查询符合age等于6的第一条数据 // doc是查询结果 }); ``` #### findById(按ID单条数据) >语法 >findById(_id, callback) 与findOne相同,但它只接收文档的_id作为参数,返回单个文档 语法 与原生不同的是,原生需要使用`ObjectId()` ``` PersonModel.findById(person._id, function (err, doc){ //doc 查询结果文档 }); ``` #### $gt、$lt(大于、小于) 查询时我们经常会碰到要根据某些字段进行条件筛选查询,比如说Number类型,怎么办呢,我们就可以使用$gt(>)、$lt(<)、$lte(<=)、$gte(>=)操作符进行排除性的查询,如下示例: ``` 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(不等于) $ne(!=)操作符的含义相当于不等于、不包含,查询时我们可通过它进行条件判定,具体使用方法如下: ``` Model.find({ age:{ $ne:6}},function(error,docs){ //查询age不等于6的所有数据 }); ``` #### $in(包含) 和$ne操作符相反,$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":"zfpx"},{"age":6}]},function(error,docs){ //查询name为zfpx或age为6的全部文档 }); ``` #### $exists(是否存在) $exists操作符,可用于判断某些关键字段是否存在来进行条件查询。如下示例: ``` Model.find({name: {$exists: true}},function(error,docs){ //查询所有存在name属性的文档 }); Model.find({email: {$exists: false}},function(error,docs){ //查询所有不存在email属性的文档 }); ``` ### 高级查询 可以限制结果的数量,跳过部分结果,根据任意键对结果进行各种排序 所有这些选项都要在查询被发送到服务器之前指定 #### limit(限制数量) >语法 >find(Conditions,fields,options,callback); 在查询操作中,有时数据量会很大,这时我们就需要对返回结果的数量进行限制 那么我们就可以使用limit函数,通过它来限制结果数量。 语法 ``` Model.find({},null,{limit:20},function(err,docs){ console.log(docs); }); ``` 如果匹配的结果不到20个,则返回匹配数量的结果,也就是说limit函数指定的是上限而非下限 #### skip(跳过/略过的数量) >语法 >find(Conditions,fields,options,callback); skip函数的功能是略过指定数量的匹配结果,返回余下的查询结果 如下示例: ``` Model.find({},null,{skip:4},function(err,docs){ console.log(docs); }); ``` 如果查询结果数量中少于4个的话,则不会返回任何结果。 #### sort函数 >语法 >find(Conditions,fields,options,callback) sort函数可以将查询结果数据进行排序操作 该函数的参数是一个或多个键/值对 键代表要排序的键名,值代表排序的方向,1是升序,-1是降序 语法 ``` Model.find({},null,{sort:{age:-1}},function(err,docs){ //查询所有数据,并按照age降序顺序返回数据docs }); ``` sort函数可根据用户自定义条件有选择性的来进行排序显示数据结果。 #### 分页查询 ``` 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/201606blog'); //定义课程Schema var CourseSchema = new mongoose.Schema({ name:String }); var CourseModel = mongoose.model('Course',CourseSchema); var PersonSchema = new mongoose.Schema({ name:{ type:String, required:true }, // 外键 别的集合的主键 course:{ type:mongoose.Schema.Types.ObjectId, ref:'Course' //指明此外键是哪个集合中的外键 } }); var PersonModel = mongoose.model('Person',PersonSchema); CourseModel.create({name:'node.js'},function(err,course){ PersonModel.create({name:'ahhh',course:course._id},function(err,doc){ console.log(doc); PersonModel.findById(doc._id).populate('course').exec(function(err,doc){ console.log(doc); //连表查询后的结果 }) }) }); ``` ## 扩展mongoose模型 ### statics 对类进行扩展 根据用户名查找用户文档 ``` //this指向model PersonSchema.statics.findByUsername = function (username, callback) { return this.findOne({ username }, callback); } Person.findByUsername('ahhh', 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: 'ahhh', password: '123456', phone: '010-6255889', firstname: 'first', lastname: 'last' }); person.exist(function (err, doc) { console.log(err, doc); }); ``` ### virtual - virtual是虚拟属性的意思,即原来Schema定义里是不存在该属性,后来通过virutal方法赋予的属性。 - 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: 'ahhh', 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指向实例 this.password = crypto.createHmac('sha256', 'ahhh').update(this.password).digest('hex'); next(); }); PersonSchema.statics.login = function (username, password, callback) { password = crypto.createHmac('sha256', 'ahhh').update(password).digest('hex'); return this.findOne({ username, password }, callback); } Person.login('ahhh', '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'); let Person = new Schema({}); Person.plugin(plugin,{index:true}); ``` - Person 是用户自己定义的Schema - Person.plugin 是为Person增加plugin - plugin有2个参数 - 插件对象 plugin - 配置项 {index:true} ``` schema.add({age:Number}); //往schema里添加一个验证字段 ```