Mongoose 用于操作mongoDB的工具,mongodb提供的原驱动也可以,但是较难操作,故使用mongoose
Mongoose 入门参考
转载说明
源于Mongoose学习参考文档——基础篇
源于Node.js 手册查询-3-Mongoose 方法
名词解释
- Schema : 一种以文件形式存储的数据库模型骨架,不具备数据库的操作能力
- Model : 由Schema发布生成的模型,具有抽象属性和行为的数据库操作对
- Entity : 由Model创建的实体,他的操作也会影响数据库
约定
本学习文档采用严格命名方式来区别不同对象,例如:
var PersonSchema;
//Person的文本属性var PersonModel;
//Person的数据库模型var PersonEntity;
//Person实体
Schema、Model、Entity的关系请牢记,Schema生成Model,Model创造Entity,Model和Entity都可对数据库操作造成影响,但Model比Entity更具操作性。
Schema
一种以文件形式存储的数据库模型骨架,无法直接通往数据库端,也就是说它不具备对数据库的操作能力.可以说是数据属性模型(传统意义的表结构),又或着是“集合”的模型骨架
1 | /* 定义一个 Schema */ |
上面这个 TestSchema包含4个属性 [name, age, time, email]
Model
由Schema构造生成的模型,除了Schema定义的数据库骨架以外,还具有数据库操作的行为,类似于管理数据库属性、行为的类
1 | var db = mongoose.connect("mongodb://127.0.0.1:27017/test"); |
Entity
由Model创建的实体,使用save方法保存数据,Model和Entity都有能影响数据库的操作,但Model比Entity更具操作性
1 | var TestEntity = new TestModel({ |
可以使用的数据类型
数据库存储的各种数据是需要预先设置其数据类型的,并且需要在Scheme中设置,我们称这些数据类型为Scheme.Type
。
Schema.Type
是由Mongoose内定的一些数据类型,基本数据类型都在其中,他也内置了一些Mongoose特有的Schema.Type
。当然,你也可以自定义Schema.Type
,只有满足Schema.Type
的类型才能定义在Schema内。
例如:
1 | var ExampleSchema = new Schema({ |
关于Buffer
Buffer和ArrayBuffer是Nodejs两种隐藏的对象,相关内容请查看NodeJS-API
关于Mixed
Schema.Types.Mixed是Mongoose定义个混合类型,该混合类型如果未定义具体形式。因此,如果定义具体内容,就直接使用{}来定义,以下两句等价
var AnySchema = new Schema({any:{}});
var AnySchema = new Schema({any:Schema.Types.Mixed});
混合类型因为没有特定约束,因此可以任意修改,一旦修改了原型,则必须调用markModified()
person.anything = {x:[3,4,{y:'change'}]}
person.markModified('anything');//传入anything,表示该属性类型发生变化
person.save();
关于ObjectId
存储在mongodb集合中的每个文档(document)都有一个默认的主键_id,这个主键名称是固定的,它可以是mongodb支持的任何数据类型,默认是ObjectId。除非自己定义,方可覆盖
ObjectId是一个12字节的 BSON 类型字符串。按照字节顺序,依次代表:
- 4字节:UNIX时间戳
- 3字节:表示运行MongoDB的机器
- 2字节:表示生成此_id的进程
- 3字节:由一个随机数开始的计数器生成的值该类型的值由系统自己生成,从某种意义上几乎不会重复,生成过程比较复杂,有兴趣的朋友可以查看源码。
1
2
3
4var mongoose = require('mongoose');
var ObjectId = mongoose.Schema.Types.ObjectId;
var StudentSchema = new Schema({}); //默认会有_id:ObjectId
var TeacherSchema = new Schema({id:ObjectId});//只有id:ObjectId
关于Array
Array在JavaScript编程语言中并不是数组,而是集合,因此里面可以存入不同的值,以下代码等价:
var ExampleSchema1 = new Schema({array:[]});
var ExampleSchema2 = new Schema({array:Array});
var ExampleSchema3 = new Schema({array:[Schema.Types.Mixed]});
var ExampleSchema4 = new Schema({array:[{}]});
附言
Schema不仅定义了文档结构和使用性能,还可以有扩展插件、实例方法、静态方法、复合索引、文档生命周期钩子
Schema可以定义插件,并且插件具有良好的可拔插性,请有兴趣的读者继续往后阅读或者查阅官方资料。
API
连接接数据库
1 | var mongoose = require("mongoose"); |
db - 数据库操作
挂接数据库连接事件,参数1: 也可以是error.
1 | db.connection.on(‘open’, callback); |
Schema - 表结构
1.构造函数
1 | new mongoose.Schema( { name:{type:String}, age:{type:Number, default:10} } ) |
2.添加属性
1 | Schema.add( { name: ‘String’, email: ‘String’, age: ‘Number’ } ) |
3.有时候Schema不仅要为后面的Model和Entity提供公共的属性,还要提供公共的方法
1 | Schema.method( ‘say’, function(){console.log(‘hello’);} ) |
4.添加静态方法
1 | Schema.static( ‘say’, function(){console.log(‘hello’);} ) |
5.追加方法
1 | Schema.methods.say = function(){console.log(‘hello’);}; |
model - 文档操作
1.构造函数, 参数1:集合名称, 参数2:Schema实例
1 | db.model(“test1”, TestSchema ); |
2.查询, 参数1忽略,或为空对象则返回所有集合文档
1 | model.find({}, callback); |
1 | model.find({},field,callback); |
1 | model.find({},null,{limit:20}); |
1 | model.findOne({}, callback); |
1 | model.findById(‘obj._id’, callback); |
3.创建, 在集合中创建一个文档
1 | Model.create(文档数据, callback)) |
4.更新,参数1:查询条件, 参数2:更新对象,可以使用MondoDB的更新修改器
1 | Model.update(conditions, update, function(error) |
5.删除, 参数1:查询条件
1 | Model.remove(conditions,callback); |
Entity - 文档操作
1.构造函数, 其实就是model的实例
1 | new TestModel( { name:‘xueyou’, age:21 } ); |
2.创建, 在集合中创建一个文档.
1 | Entity.save(callback); |
CRUD进一步说明
查询
通常有2种查询方式,一种是直接查询,一种是链式查询(2种查询都是自己命名的)
直接查询
在查询时带有回调函数的,称之为直接查询,查询的条件往往通过API来设定,例如:
PersonModel.findOne({'name.last':'dragon'},'some select',function(err,person){
//如果err==null,则person就能取到数据
});
具体的查询参数,请查询API
链式查询
在查询时候,不带回调,而查询条件通过API函数来制定,例如:
var query = PersonModel.findOne({'name.last':'dragon'});
query.select('some select');
query.exec(function(err,pserson){
//如果err==null,则person就能取到数据
});
这种方式相对直接查询,分的比较明细,如果不带callback,则返回query,query没有执行的预编译查询语句,该query对象执行的方法都将返回自己,只有在执行exec方法时才执行查询,而且必须有回调。
因为query的操作始终返回自身,我们可以采用更形象的链式写法
Person
.find({ occupation: /host/ })
.where('name.last').equals('Ghost')
.where('age').gt(17).lt(66)
.where('likes').in(['vaporizing', 'talking'])
.limit(10)
.sort('-occupation')
.select('name occupation')
.exec(callback);
更新
有许多方式来更新文件,以下是常用的传统方式:
1 | PersonModel.findById(id,function(err,person){ |
这里,利用Model模型查询到了person对象,该对象属于Entity,可以有save操作,如果使用Model`操作,需注意:
1 | PersonModel.findById(id,function(err,person){ |
update
第一个参数是查询条件,第二个参数是更新的对象,但不能更新主键,这就是为什么要删除主键的原因。
当然这样的更新很麻烦,可以使用$set属性来配置,这样也不用先查询,如果更新的数据比较少,可用性还是很好的:
PersonModel.update({_id:_id},{$set:{name:'MDragon'}},function(err){});
需要注意,Document的CRUD操作都是异步执行,callback第一个参数必须是err,而第二个参数各个方法不一样,update的callback第二个参数是更新的数量,如果要返回更新后的对象,则要使用如下方法
Person.findByIdAndUpdate(_id,{$set:{name:'MDragon'}},function(err,person){
console.log(person.name); //MDragon
});
类似的方法还有findByIdAndRemove,如同名字,只能根据id查询并作update/remove操作,操作的数据仅一条
新增
如果是Entity,使用save方法,如果是Model,使用create方法
//使用Entity来增加一条数据
var krouky = new PersonModel({name:'krouky'});
krouky.save(callback);
//使用Model来增加一条数据
var MDragon = {name:'MDragon'};
PersonModel.create(MDragon,callback);
两种新增方法区别在于,如果使用Model新增时,传入的对象只能是纯净的JSON对象,不能是由Model创建的实体,原因是:由Model创建的实体krouky虽然打印是只有{name:'krouky'}
,但是krouky属于Entity,包含有Schema属性和Model数据库行为模型。如果是使用Model创建的对象,传入时一定会将隐藏属性也存入数据库,虽然3.x追加了默认严格属性,但也不必要增加操作的报错
删除
和新增一样,删除也有2种方式,但Entity和Model都使用remove方法
Sub Docs
如同SQL数据库中2张表有主外关系,Mongoose将2个Document的嵌套叫做Sub-Docs(子文档)
简单的说就是一个Document嵌套另外一个Document或者Documents:
var ChildSchema1 = new Schema({name:String});
var ChildSchema2 = new Schema({name:String});
var ParentSchema = new Schema({
children1:ChildSchema1, //嵌套Document
children2:[ChildSchema2] //嵌套Documents
});
Sub-Docs享受和Documents一样的操作,但是Sub-Docs的操作都由父类去执行
var ParentModel = db.model('Parent',parentSchema);
var parent = new ParentModel({
children2:[{name:'c1'},{name:'c2'}]
});
parent.children2[0].name = 'd';
parent.save(callback);
parent在执行保存时,由于包含children2,他是一个数据库模型对象,因此会先保存chilren2[0]和chilren2[1]。
如果子文档在更新时出现错误,将直接报在父类文档中,可以这样处理:
ChildrenSchema.pre('save',function(next){
if('x' === this.name) return next(new Error('#err:not-x'));
next();
});
var parent = new ParentModel({children1:{name:'not-x'}});
parent.save(function(err){
console.log(err.message); //#err:not-x
});
查询子文档
如果children是parent的子文档,可以通过如下方法查询到children
var child = parent.children.id(id);
新增、删除、更新
子文档是父文档的一个属性,因此按照属性的操作即可,不同的是在新增父类的时候,子文档是会被先加入进去的。
如果ChildrenSchema是临时的一个子文档,不作为数据库映射集合,可以这样:
var ParentSchema = new Schema({
children:{
name:String
}
});
//其实就是匿名混合模式
修改器和更新器
更新修改器:
‘$inc’ 增减修改器,只对数字有效.下面的实例: 找到 age=22的文档,修改文档的age值自增1
1
2Model.update({‘age’:22}, {’$inc’:{‘age’:1} } );
//执行后: age=23‘$set’ 指定一个键的值,这个键不存在就创建它.可以是任何MondoDB支持的类型.
1
2Model.update({‘age’:22}, {’$set’:{‘age’:‘haha’} } );
//执行后: age=‘haha’‘$unset’ 同上取反,删除一个键
1
2Model.update({‘age’:22}, {’$unset’:{‘age’:‘haha’} } );
//执行后: age键不存在数组修改器:
‘$push’ 给一个键push一个数组成员,键不存在会创建
1
2Model.update({‘age’:22}, {’$push’:{‘array’:10} } );
//执行后: 增加一个 array 键,类型为数组, 有一个成员 10‘$addToSet’ 向数组中添加一个元素,如果存在就不添加
1
2Model.update({‘age’:22}, {’$addToSet’:{‘array’:10} } );
//执行后: array中有10所以不会添加‘$each’ 遍历数组, 和 $push 修改器配合可以插入多个值
1
2Model.update({‘age’:22}, {’$push’:{‘array’:{’$each’: [1,2,3,4,5]}} } );
//执行后: array : [10,1,2,3,4,5]‘$pop’ 向数组中尾部删除一个元素
1
2Model.update({‘age’:22}, {’$pop’:{‘array’:1} } );
//执行后: array : [10,1,2,3,4] tips: 将1改成-1可以删除数组首部元素‘$pull’ 向数组中删除指定元素
1
2Model.update({‘age’:22}, {’$pull’:{‘array’:10} } );
//执行后: array : [1,2,3,4] 匹配到array中的10后将其删除条件查询:
“$lt” 小于
“$lte” 小于等于
“$gt” 大于
“$gte” 大于等于
“$ne” 不等于
Model.find({“age”:{ “$get”:18 , “$lte”:30 } } );
查询 age 大于等于18并小于等于30的文档
或查询 OR:
‘$in’ 一个键对应多个值
‘$nin’ 同上取反, 一个键不对应指定值
“$or” 多个条件匹配, 可以嵌套 $in 使用
“$not” 同上取反, 查询与特定模式不匹配的文档
1 | Model.find({“age”:{ “$in”:[20,21,22.‘haha’]} } ); |
1 | Model.find({"$or" : [ {‘age’:18} , {‘name’:‘xueyou’} ] }); |
类型查询:
null 能匹配自身和不存在的值, 想要匹配键的值 为null, 就要通过 “$exists” 条件判定键值已经存在
“$exists” (表示是否存在的意思)
1 | Model.find(“age” : { “$in” : [null] , “exists” : true } ); |
1 | Model.find({name: {$exists: true}},function(error,docs){ |
1 | Model.find({telephone: {$exists: false}},function(error,docs){ |
正则表达式:
MongoDb 使用 Prel兼容的正则表达式库来匹配正则表达式
1 | find( {“name” : /joe/i } ) |
1 | find( {“name” : /joe?/i } ) |
查询数组:
1 | Model.find({“array”:10} ); |
1 | Model.find({“array[5]”:10} ); |
- ‘$all’ 匹配数组中多个元素
1
2Model.find({“array”:[5,10]} );
//查询 匹配array数组中 既有5又有10的文档 - ‘$size’ 匹配数组长度
1
2Model.find({“array”:{"$size" : 3} } );
//查询 匹配array数组长度为3 的文档 - ‘$slice’ 查询子集合返回
1
2Model.find({“array”:{"$skice" : 10} } );
//查询 匹配array数组的前10个元素1
2Model.find({“array”:{"$skice" : [5,10] } } );
//查询 匹配array数组的第5个到第10个元素where
用它可以执行任意javacript语句作为查询的一部分,如果回调函数返回 true 文档就作为结果的一部分返回
1 | find( {"$where" : function(){ |
简化版本
1 | find( {"$where" : "this.x + this.y === 10" } ) |
游标
MongoDB 使用游标返回find的执行结果.客户端对游标的实现通常能够对最终结果进行有效的控制。可以限制结果的数量,略过部分结果,根据任意键按任意顺序的组合对结果进行各种排序,或者是执行其他一些强的操作。
- limit(3) 限制返回结果的数量,
- skip(3) 跳过前3个文档,返回其余的
- sort( {“username”:1 , “age”:-1 } ) 排序 键对应文档的键名, 值代表排序方向, 1 升序, -1降序