资讯专栏INFORMATION COLUMN

(转)Mongoose学习参考文档

Sleepy / 1002人阅读

摘要:转自一快速通道名词解释一种以文件形式存储的数据库模型骨架,不具备数据库的操作能力由发布生成的模型,具有抽象属性和行为的数据库操作对由创建的实体,他的操作也会影响数据库注意本学习文档采用严格命名方式来区别不同对象,例如的文本属性的数据库模型实

转自 https://cnodejs.org/topic/504...

一、快速通道 1.1 名词解释

Schema : 一种以文件形式存储的数据库模型骨架,不具备数据库的操作能力

Model : 由Schema发布生成的模型,具有抽象属性和行为的数据库操作对

Entity : 由Model创建的实体,他的操作也会影响数据库

注意:

1.本学习文档采用严格命名方式来区别不同对象,例如:

</>复制代码

  1. var PersonSchema; //Person的文本属性
  2. var PersonModel; //Person的数据库模型
  3. var PersonEntity; //Person实体

2.Schema、Model、Entity的关系请牢记,Schema生成Model,Model创造Entity,Model和Entity都可对数据库操作造成影响,但Model比Entity更具操作性。

1.2 准备工作

1.首先你必须安装MongoDB和NodeJS

2.在项目只能够创建一个数据库连接,如下:

</>复制代码

  1. var mongoose = require("mongoose"); //引用mongoose模块
  2. var db = mongoose.createConnection("localhost","test"); //创建一个数据库连接

3.打开本机localhost的test数据库时,我们可以监测是否有异常

</>复制代码

  1. db.on("error",console.error.bind(console,"连接错误:"));
  2. db.once("open",function(){
  3. //一次打开记录
  4. });

注意:

成功开启数据库后,就可以执行数据库相应操作,假设以下代码都在回调中处理

4.定义一个Schema

</>复制代码

  1. var PersonSchema = new mongoose.Schema({
  2. name:String //定义一个属性name,类型为String
  3. });

5.将该Schema发布为Model

</>复制代码

  1. var PersonModel = db.model("Person",PersonSchema);
  2. //如果该Model已经发布,则可以直接通过名字索引到,如下:
  3. //var PersonModel = db.model("Person");
  4. //如果没有发布,上一段代码将会异常

6.用Model创建Entity

</>复制代码

  1. var personEntity = new PersonModel({name:"Krouky"});
  2. //打印这个实体的名字看看
  3. console.log(personEntity.name); //Krouky

7.我们甚至可以为此Schema创建方法

</>复制代码

  1. //为Schema模型追加speak方法
  2. PersonSchema.methos.speak = function(){
  3. console.log("我的名字叫"+this.name);
  4. }
  5. var PersonModel = db.model("Person",PersonSchema);
  6. var personEntity = new PersonModel({name:"Krouky"});
  7. personEntity.speak();//我的名字叫Krouky

8.Entity是具有具体的数据库操作CRUD的

</>复制代码

  1. personEntity.save(); //执行完成后,数据库就有该数据了

9.如果要执行查询,需要依赖Model,当然Entity也是可以做到的

</>复制代码

  1. PersonModel.find(function(err,persons){
  2. //查询到的所有person
  3. });

注意:

具体的如何配置Schema、Model以及Model和Entity的相关操作,我们会在后面进行

Model和Entity都有能影响数据库的操作,但仍有区别,后面我们也会做解释

二、新手指引

如果您还不清楚Mongoose是如何工作的,请参看第一章快速通道快速浏览他的用法吧

1. Schema——纯洁的数据库原型 1.1 什么是Schema

我理解Schema仅仅只是一断代码,他书写完成后程序依然无法使用,更无法通往数据库端
他仅仅只是数据库模型在程序片段中的一种表现,或者是数据属性模型

1.2 如何定义Schema

</>复制代码

  1. var BlogSchema = new Schema({
  2. title:String,
  3. author:String
  4. //new Schema()中传入一个JSON对象,该对象形如 xxx:yyyy ,
  5. /xxx是一个字符串,定义了属性,yyy是一个Schema.Type,定义了属性类型
  6. });
1.3 什么是Schema.Type

Schema.Type是由Mongoose内定的一些数据类型,基本数据类型都在其中,他也内置了一些Mongoose特有的Schema.Type。当然,你也可以自定义Schema.Type,只有满足Schema.Type的类型才能定义在Schema内。

1.4 Schema.Types

NodeJS中的基本数据类型都属于Schema.Type,另外Mongoose还定义了自己的类型

</>复制代码

  1. //举例:
  2. var ExampleSchema = new Schema({
  3. name:String,
  4. binary:Buffer,
  5. living:Boolean,
  6. updated:Date,
  7. age:Number,
  8. mixed:Schema.Types.Mixed, //该混合类型等同于nested
  9. _id:Schema.Types.ObjectId, //主键
  10. _fk:Schema.Types.ObjectId, //外键
  11. array:[],
  12. arrOfString:[String],
  13. arrOfNumber:[Number],
  14. arrOfDate:[Date],
  15. arrOfBuffer:[Buffer],
  16. arrOfBoolean:[Boolean],
  17. arrOfMixed:[Schema.Types.Mixed],
  18. arrOfObjectId:[Schema.Types.ObjectId]
  19. nested:{
  20. stuff:String,
  21. }
  22. });
1.5 关于Buffer

Buffer和ArrayBuffer是Nodejs两种隐藏的对象,相关内容请查看NodeJS-API

1.6 关于Mixed

Schema.Types.Mixed是Mongoose定义个混合类型,该混合类型如果未定义具体形式。因此,如果定义具体内容,就直接使用{}来定义,以下两句等价

</>复制代码

  1. var AnySchema = new Schema({any:{}});
  2. var AnySchema = new Schema({any:Schema.Types.Mixed});

混合类型因为没有特定约束,因此可以任意修改,一旦修改了原型,则必须调用markModified()

</>复制代码

  1. person.anything = {x:[3,4,{y:"change"}]}
  2. person.markModified("anything");//传入anything,表示该属性类型发生变化
  3. person.save();
1.7 关于ObjectId

主键,一种特殊而且非常重要的类型,每个Schema都会默认配置这个属性,属性名为_id,除非自己定义,方可覆盖

</>复制代码

  1. var mongoose = require("mongoose");
  2. var ObjectId = mongoose.Schema.Types.ObjectId;
  3. var StudentSchema = new Schema({}); //默认会有_id:ObjectId
  4. var TeacherSchema = new Schema({id:ObjectId});//只有id:ObjectId

该类型的值由系统自己生成,从某种意义上几乎不会重复,生成过程比较复杂,有兴趣的朋友可以查看源码。

1.8 关于Array

Array在JavaScript编程语言中并不是数组,而是集合,因此里面可以存入不同的值,以下代码等价:

</>复制代码

  1. var ExampleSchema1 = new Schema({array:[]});
  2. var ExampleSchema2 = new Schema({array:Array});
  3. var ExampleSchema3 = new Schema({array:[Schema.Types.Mixed]});
  4. var ExampleSchema4 = new Schema({array:[{}]});
1.9 附言

Schema不仅定义了文档结构和使用性能,还可以有扩展插件、实例方法、静态方法、复合索引、文档生命周期钩子

Schema可以定义插件,并且插件具有良好的可拔插性,请有兴趣的读者继续往后阅读或者查阅官方资料。

2. Schema的扩展 2.1 实例方法

有的时候,我们创造的Schema不仅要为后面的Model和Entity提供公共的属性,还要提供公共的方法。

下面例子比快速通道的例子更加高级,可以进行高级扩展:

</>复制代码

  1. var PersonSchema = new Schema({name:String,type:String});
  2. //查询类似数据
  3. PersonSchema.methods.findSimilarTypes = function(cb){
  4. return this.model("Person").find({type:this.type},cb);
  5. }

使用如下:

</>复制代码

  1. var PersonModel = mongoose.model("Person",PersonSchema);
  2. var krouky = new PersonSchema({name:"krouky",type:"前端工程师"});
  3. krouky.findSimilarTypes(function(err,persons){
  4. //persons中就能查询到其他前端工程师
  5. });
2.2 静态方法

静态方法在Model层就能使用,如下:

PersonSchema.statics.findByName = function(name,cb){

</>复制代码

  1. this.find({name:new RegExp(name,"i"),cb});

}
var PersonModel = mongoose.model("Person",PersonSchema);
PersonModel.findByName("krouky",function(err,persons){

</>复制代码

  1. //找到所有名字叫krouky的人

});

2.3 索引

索引或者复合索引能让搜索更加高效,默认索引就是主键索引ObjectId,属性名为_id, 索引会作为一个专题来讲解

2.4 虚拟属性

Schema中如果定义了虚拟属性,那么该属性将不写入数据库,例如:

</>复制代码

  1. var PersonSchema = new Schema({
  2. name:{
  3. first:String,
  4. last:String
  5. }
  6. });
  7. var PersonModel = mongoose.model("Person",PersonSchema);
  8. var krouky = new PersonModel({
  9. name:{first:"krouky",last:"han"}
  10. });

如果每次想使用全名就得这样

</>复制代码

  1. console.log(krouky.name.first + " " + krouky.name.last);

显然这是很麻烦的,我们可以定义虚拟属性:

</>复制代码

  1. PersonSchema.virtual("name.full").get(function(){
  2. return this.name.first + " " + this.name.last;
  3. });

那么就能用krouky.name.full来调用全名了,反之如果知道full,也可以反解first和last属性

</>复制代码

  1. PersonSchema.virtual("name.full").set(function(name){
  2. var split = name.split(" ");
  3. this.name.first = split[0];
  4. this.name.last = split[1];
  5. });
  6. var PersonModel = mongoose.model("Person",PersonSchema);
  7. var krouky = new PersonModel({});
  8. krouky.name.full = "krouky han";//会被自动分解
  9. console.log(krouky.name.first);//krouky
2.5 配置项

在使用new Schema(config)时,我们可以追加一个参数options来配置Schema的配置,形如:

</>复制代码

  1. var ExampleSchema = new Schema(config,options);

或者使用

</>复制代码

  1. var ExampleSchema = new Schema(config);
  2. ExampleSchema.set(option,value);

可供配置项有:safe、strict、capped、versionKey、autoIndex

2.5.1 safe——安全属性(默认安全)

一般可做如下配置:

</>复制代码

  1. new Schema({...},{safe:true});

当然我们也可以这样

</>复制代码

  1. new Schema({...},{safe:{j:1,w:2,wtimeout:10000}});

j表示做1份日志,w表示做2个副本(尚不明确),超时时间10秒

2.5.2 strict——严格配置(默认启用)

确保Entity的值存入数据库前会被自动验证,如果你没有充足的理由,请不要停用,例子:

</>复制代码

  1. var ThingSchema = new Schema({a:String});
  2. var ThingModel = db.model("Thing",SchemaSchema);
  3. var thing = new Thing({iAmNotInTheThingSchema:true});
  4. thing.save();//iAmNotInTheThingSchema这个属性将无法被存储

如果取消严格选项,iAmNotInTheThingSchema将会被存入数据库

该选项也可以在构造实例时使用,例如:

</>复制代码

  1. var ThingModel = db.model("Thing");
  2. var thing1 = new ThingModel(doc,true); //启用严格
  3. var thing2 = new ThingModel(doc,false); //禁用严格

注意:

strict也可以设置为throw,表示出现问题将会抛出错误

2.5.3 shardKey

需要mongodb做分布式,才会使用该属性

2.5.4 capped——上限设置

如果有数据库的批量操作,该属性能限制一次操作的量,例如:

</>复制代码

  1. new Schema({...},{capped:1024}); //一次操作上线1024条数据

当然该参数也可是JSON对象,包含size、max、autiIndexId属性

</>复制代码

  1. new Schema({...},{capped:{size:1024,max:100,autoIndexId:true}});
2.5.5 versionKey——版本锁

版本锁是Mongoose默认配置(__v属性)的,如果你想自己定制,如下:

</>复制代码

  1. new Schema({...},{versionKey:"__someElse"});

此时存入数据库的版本锁就不是__v属性,而是__someElse,相当于是给版本锁取名字。

具体怎么存入都是由Mongoose和MongoDB自己决定,当然,这个属性你也可以去除

new Schema({...},{versionKey:false});
除非你知道你在做什么,并且你知道这样做的后果

2.5.6 autoIndex——自动索引

该内容将在索引章节多带带讲解

3. Documents

Document是与MongoDB文档一一对应的模型,Document可等同于Entity,具有属性和操作性

注意:

Document的`CRUD都必须经过严格验证的,参看2.5.2 Schema的strict严格配置

3.1 查询

查询内容过多,专题讲解

3.2 更新

有许多方式来更新文件,以下是常用的传统方式:

</>复制代码

  1. PersonModel.findById(id,function(err,person){
  2. person.name = "MDragon";
  3. person.save(function(err){});
  4. });

这里,利用Model模型查询到了person对象,该对象属于Entity,可以有save操作,如果使用Model`操作,需注意:

</>复制代码

  1. PersonModel.findById(id,function(err,person){
  2. person.name = "MDragon";
  3. var _id = person._id; //需要取出主键_id
  4. delete person._id; //再将其删除
  5. PersonModel.update({_id:_id},person,function(err){});
  6. //此时才能用Model操作,否则报错
  7. });

update第一个参数是查询条件,第二个参数是更新的对象,但不能更新主键,这就是为什么要删除主键的原因。

当然这样的更新很麻烦,可以使用$set属性来配置,这样也不用先查询,如果更新的数据比较少,可用性还是很好的:

</>复制代码

  1. PersonModel.update({_id:_id},{$set:{name:"MDragon"}},function(err){});

需要注意,Document的CRUD操作都是异步执行,callback第一个参数必须是err,而第二个参数各个方法不一样,update的callback第二个参数是更新的数量,如果要返回更新后的对象,则要使用如下方法

</>复制代码

  1. Person.findByIdAndUpdate(_id,{$set:{name:"MDragon"}},function(err,person){
  2. console.log(person.name); //MDragon
  3. });

类似的方法还有findByIdAndRemove,如同名字,只能根据id查询并作update/remove操作,操作的数据仅一条

3.3 新增

如果是Entity,使用save方法,如果是Model,使用create方法

</>复制代码

  1. //使用Entity来增加一条数据
  2. var krouky = new PersonModel({name:"krouky"});
  3. krouky.save(callback);
  4. //使用Model来增加一条数据
  5. var MDragon = {name:"MDragon"};
  6. PersonModel.create(MDragon,callback);

两种新增方法区别在于,如果使用Model新增时,传入的对象只能是纯净的JSON对象,不能是由Model创建的实体,原因是:由Model创建的实体krouky虽然打印是只有{name:"krouky"},但是krouky属于Entity,包含有Schema属性和Model数据库行为模型。如果是使用Model创建的对象,传入时一定会将隐藏属性也存入数据库,虽然3.x追加了默认严格属性,但也不必要增加操作的报错

3.4 删除

和新增一样,删除也有2种方式,但Entity和Model都使用remove方法

4.Sub Docs

如同SQL数据库中2张表有主外关系,Mongoose将2个Document的嵌套叫做Sub-Docs(子文档)

简单的说就是一个Document嵌套另外一个Document或者Documents:

</>复制代码

  1. var ChildSchema1 = new Schema({name:String});
  2. var ChildSchema2 = new Schema({name:String});
  3. var ParentSchema = new Schema({
  4. children1:ChildSchema1, //嵌套Document
  5. children2:[ChildSchema2] //嵌套Documents
  6. });

Sub-Docs享受和Documents一样的操作,但是Sub-Docs的操作都由父类去执行

</>复制代码

  1. var ParentModel = db.model("Parent",parentSchema);
  2. var parent = new ParentModel({
  3. children2:[{name:"c1"},{name:"c2"}]
  4. });
  5. parent.children2[0].name = "d";
  6. parent.save(callback);

parent在执行保存时,由于包含children2,他是一个数据库模型对象,因此会先保存chilren2[0]和chilren2[1]。

如果子文档在更新时出现错误,将直接报在父类文档中,可以这样处理:

</>复制代码

  1. ChildrenSchema.pre("save",function(next){
  2. if("x" === this.name) return next(new Error("#err:not-x"));
  3. next();
  4. });
  5. var parent = new ParentModel({children1:{name:"not-x"}});
  6. parent.save(function(err){
  7. console.log(err.message); //#err:not-x
  8. });
4.1 查询子文档

如果children是parent的子文档,可以通过如下方法查询到children

</>复制代码

  1. var child = parent.children.id(id);
4.2 新增、删除、更新

子文档是父文档的一个属性,因此按照属性的操作即可,不同的是在新增父类的时候,子文档是会被先加入进去的。

如果ChildrenSchema是临时的一个子文档,不作为数据库映射集合,可以这样:

</>复制代码

  1. var ParentSchema = new Schema({
  2. children:{
  3. name:String
  4. }
  5. });
  6. //其实就是匿名混合模式
5.Model 5.1 什么是Model

Model模型,是经过Schema构造来的,除了Schema定义的数据库骨架以外,还具有数据库行为模型,他相当于管理数据库属性、行为的类

5.2 如何创建Model

你必须通过Schema来创建,如下:

</>复制代码

  1. //先创建Schema
  2. var TankSchema = new Schema({
  3. name:"String",
  4. size:"String"
  5. });
  6. //通过Schema创建Model
  7. var TankModel = mongoose.model("Tank",TankSchema);
5.2 操作Model

该模型就能直接拿来操作,具体查看API,例如:

</>复制代码

  1. var tank = {"something",size:"small"};
  2. TankModel.create(tank);

注意:

你可以使用Model来创建Entity,Entity实体是一个特有Model具体对象,但是他并不具备Model的方法,只能用自己的方法。

//通过Model创建Entity
var tankEntity = new TankModel("someother","size:big");
tankEntity.save();

6.Query

查询是数据库中运用最多也是最麻烦的地方,这里对Query解读的并不完善,仅仅是自己的一点领悟而已。

6.1 查询的方式

通常有2种查询方式,一种是直接查询,一种是链式查询(2种查询都是自己命名的)

6.1.1 直接查询

在查询时带有回调函数的,称之为直接查询,查询的条件往往通过API来设定,例如:

</>复制代码

  1. PersonModel.findOne({"name.last":"dragon"},"some select",function(err,person){
  2. //如果err==null,则person就能取到数据
  3. });

具体的查询参数,请查询API

6.1.2 链式查询

在查询时候,不带回调,而查询条件通过API函数来制定,例如:

</>复制代码

  1. var query = PersonModel.findOne({"name.last":"dragon"});
  2. query.select("some select");
  3. query.exec(function(err,pserson){
  4. //如果err==null,则person就能取到数据

});
这种方式相对直接查询,分的比较明细,如果不带callback,则返回query,query没有执行的预编译查询语句,该query对象执行的方法都将返回自己,只有在执行exec方法时才执行查询,而且必须有回调。

因为query的操作始终返回自身,我们可以采用更形象的链式写法

</>复制代码

  1. Person
  2. .find({ occupation: /host/ })
  3. .where("name.last").equals("Ghost")
  4. .where("age").gt(17).lt(66)
  5. .where("likes").in(["vaporizing", "talking"])
  6. .limit(10)
  7. .sort("-occupation")
  8. .select("name occupation")
  9. .exec(callback);
7.Validation

数据的存储是需要验证的,不是什么数据都能往数据库里丢或者显示到客户端的,数据的验证需要记住以下规则:

验证始终定义在SchemaType中
验证是一个内部中间件
验证是在一个Document被保存时默认启用的,除非你关闭验证
验证是异步递归的,如果你的SubDoc验证失败,Document也将无法保存
验证并不关心错误类型,而通过ValidationError这个对象可以访问

7.1 验证器

required 非空验证
min/max 范围验证(边值验证)
enum/match 枚举验证/匹配验证
validate 自定义验证规则
以下是综合案例:

</>复制代码

  1. var PersonSchema = new Schema({
  2. name:{
  3. type:"String",
  4. required:true //姓名非空
  5. },
  6. age:{
  7. type:"Nunmer",
  8. min:18, //年龄最小18
  9. max:120 //年龄最大120
  10. },
  11. city:{
  12. type:"String",
  13. enum:["北京","上海"] //只能是北京、上海人
  14. },
  15. other:{
  16. type:"String",
  17. validate:[validator,err] //validator是一个验证函数,err是验证失败的错误信息
  18. }
  19. });
7.2 验证失败

如果验证失败,则会返回err信息,err是一个对象该对象属性如下

</>复制代码

  1. err.errors //错误集合(对象)
  2. err.errors.color //错误属性(Schema的color属性)
  3. err.errors.color.message //错误属性信息
  4. err.errors.path //错误属性路径
  5. err.errors.type //错误类型
  6. err.name //错误名称
  7. err.message //错误消息

一旦验证失败,Model和Entity都将具有和err一样的errors属性

8.Middleware中间件 8.1 什么是中间件

中间件是一种控制函数,类似插件,能控制流程中的init、validate、save、remove`方法

8.2 中间件的分类

中间件分为两类

8.2.1 Serial串行

串行使用pre方法,执行下一个方法使用next调用

</>复制代码

  1. var schema = new Schema(...);
  2. schema.pre("save",function(next){
  3. //做点什么
  4. next();
  5. });
8.2.2 Parallel并行

并行提供更细粒度的操作

</>复制代码

  1. var schema = new Schema(...);
  2. schema.pre("save",function(next,done){
  3. //下一个要执行的中间件并行执行
  4. next();
  5. doAsync(done);
  6. });
8.3 中间件特点

一旦定义了中间件,就会在全部中间件执行完后执行其他操作,使用中间件可以雾化模型,避免异步操作的层层迭代嵌套

8.4 使用范畴

复杂的验证
删除有主外关联的doc
异步默认
某个特定动作触发异步任务,例如触发自定义事件和通知
例如,可以用来做自定义错误处理

</>复制代码

  1. schema.pre("save",function(next){
  2. var err = new Eerror("some err");
  3. next(err);
  4. });
  5. entity.save(function(err){
  6. console.log(err.message); //some err
  7. });

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/18964.html

相关文章

  • mongoose 链接数据库,增删改查

    摘要:根据个人编码习惯,仅供参考如中途看的有点蒙圈,可以转到官网查看引入模块连接数据库链接状态数据库链接成功为了查看是否链接成功所以做一个回调创建模式对象例将赋值给变量创建对象官网创建通过来创建代表的是数据库中的集合,通过才能对数据库进行操作可在 根据个人编码习惯,仅供参考 如中途看的有点蒙圈,可以转到官网查看 引入模块 var mongoose = require(mongoose); ...

    lanffy 评论0 收藏0
  • Node学习记录: mongodb

    摘要:的逻辑结构是一种层次结构,主要由文档集合数据库这三部分组成的。文档由键值对构成,像等,它是核心单元,的文档,相当于关系数据库中的一行记录。我们以此为例看它如何使用真实项目中参考学习笔记之的使用入门教程的一些操作命令 showImg(https://segmentfault.com/img/bVN45N?w=1192&h=578); 简介 在说MongoDB数据插入操作之前,我们先来简单...

    zzzmh 评论0 收藏0
  • []mongodb中的populate方法

    摘要:使用可以实现在一个中填充其他的。表示关联注意被关联的的必须是和才有效。封装了很多查询的方法,使得对数据库的操作变得简单啦。这里分享一下方法用法。类型的时,格式如为表示不填充,为时表示填充。类型,可选,指定附加的查询条件。 Mongoose 是 MongoDB 的 ODM(Object Document Mapper)。 什么是ODM? 其实和ORM(Object Relational...

    ranwu 评论0 收藏0
  • 前端学习资源整理

    稍微整理了一下自己平时看到的前端学习资源,分享给大家。 html MDN:Mozilla开发者网络 SEO:前端开发中的SEO css 张鑫旭:张鑫旭的博客 css精灵图:css精灵图实践 栅格系统:详解CSS中的栅格系统 媒体查询:css媒体查询用法 rem布局:手机端页面自适应布局 移动前端开发之viewport的深入理解:深入理解viewport 淘宝前端布局:手机淘宝移动端布局 fl...

    siberiawolf 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<