Nodejs+Express学习二(Mongoose基础了解)
学习Node
注定少不了与数据库打交道,而MongoDB
和Node
可以说是绝配,这篇主要是简单介绍Mongoose
这个模块。
由于本人也是边学边写的这篇文章,绝对会有新手的味道,请大神看到这里就表往下看了。
名词介绍
稍微翻阅下mongoose文档就会看到Schema
、Model
、Entity
这几个名词,文章主要围绕这几个词展开,所以有必要解释一下:
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
更具操作性。
下面正式步入正题:
首先你必须安装MongoDB和NodeJS
安装完成MongoDB
后,创建一个名为test
的数据库(这里不介绍怎么安装MongoDB,可以猛点这里查看MongoDB安装)
在项目中创建一个数据库连接,如下:
var mongoose = require('mongoose'); //引用mongoose模块
var db = mongoose.createConnection('localhost','test'); //创建一个数据库连接
打开本机localhost的test数据库时,我们可以监测是否有异常
db.on("error",console.error.bind(console,"连接错误:"));
db.once("open",function(){//一次打开记录
});
定义一个Schema
var PersonSchema = new mongoose.Schema({name:String //定义一个属性name,类型为String
});
将该Schema发布为Model
var PersonModel = db.model('Person',PersonSchema);
//如果该Model已经发布,则可以直接通过名字索引到,如下:
//var PersonModel = db.model('Person');
//如果没有发布,上一段代码将会异常
我们甚至可以为此Schema创建方法
//为Schema模型追加speak方法
PersonSchema.methos.speak = function(){console.log('我的名字叫'+this.name);
}
var PersonModel = db.model('Person',PersonSchema);
var personEntity = new PersonModel({name:'Krouky'});
personEntity.speak();//我的名字叫Krouky
Entity是具有具体的数据库操作CRUD的
personEntity.save(); //执行完成后,数据库就有该数据了
如果要执行查询,需要依赖Model,当然Entity也是可以做到的
PersonModel.find(function(err,persons){//查询到的所有person
});
逐一介绍
Schema
——纯洁的数据库原型
我理解Schema仅仅只是一断代码,他书写完成后程序依然无法使用,更无法通往数据库端
他仅仅只是数据库模型在程序片段中的一种表现,或者是数据属性模型
如何定义Schema
var BlogSchema = new Schema({title:String,author:String//new Schema()中传入一个JSON对象,该对象形如 xxx:yyyy ,/xxx是一个字符串,定义了属性,yyy是一个Schema.Type,定义了属性类型
});
什么是Schema.Type
NodeJS
中的基本数据类型都属于Schema.Type,另外Mongoose还定义了自己的类型
//举例:
var ExampleSchema = new Schema({name:String,binary:Buffer,living:Boolean,updated:Date,age:Number,mixed:Schema.Types.Mixed, //该混合类型等同于nested_id:Schema.Types.ObjectId, //主键_fk:Schema.Types.ObjectId, //外键array:[],arrOfString:[String],arrOfNumber:[Number],arrOfDate:[Date],arrOfBuffer:[Buffer],arrOfBoolean:[Boolean],arrOfMixed:[Schema.Types.Mixed],arrOfObjectId:[Schema.Types.ObjectId]nested:{stuff:String,}
});
关于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
主键,一种特殊而且非常重要的类型,每个Schema都会默认配置这个属性,属性名为_id
,除非自己定义,方可覆盖
var 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
可以定义插件,并且插件具有良好的可拔插性,请有兴趣的读者继续往后阅读或者查阅官方资料。
实例方法
有的时候,我们创造的Schema
不仅要为后面的Model和Entity
提供公共的属性,还要提供公共的方法。
下面例子比快速通道的例子更加高级,可以进行高级扩展:
var PersonSchema = new Schema({name:String,type:String});
//查询类似数据
PersonSchema.methods.findSimilarTypes = function(cb){return this.model('Person').find({type:this.type},cb);
}
使用如下:
var PersonModel = mongoose.model('Person',PersonSchema);
var krouky = new PersonSchema({name:'krouky',type:'前端工程师'});
krouky.findSimilarTypes(function(err,persons){//persons中就能查询到其他前端工程师
});
静态方法
静态方法在Model
层就能使用,如下:
PersonSchema.statics.findByName = function(name,cb){this.find({name:new RegExp(name,'i'),cb});}var PersonModel = mongoose.model('Person',PersonSchema);PersonModel.findByName('krouky',function(err,persons){//找到所有名字叫krouky的人});
索引
索引或者复合索引能让搜索更加高效,默认索引就是主键索引ObjectId
,属性名为_id
虚拟属性
Schema
中如果定义了虚拟属性,那么该属性将不写入数据库,例如:
var PersonSchema = new Schema({name:{first:String,last:String}
});
var PersonModel = mongoose.model('Person',PersonSchema);
var krouky = new PersonModel({name:{first:'krouky',last:'han'}
});
如果每次想使用全名就得这样
console.log(krouky.name.first + ' ' + krouky.name.last);
显然这是很麻烦的,我们可以定义虚拟属性:
PersonSchema.virtual('name.full').get(function(){return this.name.first + ' ' + this.name.last;
});
那么就能用krouky.name.full
来调用全名了,反之如果知道full
,也可以反解first
和last属性
PersonSchema.virtual('name.full').set(function(name){var split = name.split(' ');this.name.first = split[0];this.name.last = split[1];
});
var PersonModel = mongoose.model('Person',PersonSchema);
var krouky = new PersonModel({});
krouky.name.full = 'krouky han';//会被自动分解
console.log(krouky.name.first);//krouky
配置项
在使用new Schema(config)时,我们可以追加一个参数options来配置Schema的配置,形如:
var ExampleSchema = new Schema(config,options);
或者使用
var ExampleSchema = new Schema(config);
ExampleSchema.set(option,value);
可供配置项有:safe
、strict
、capped
、versionKey
、autoIndex
safe——安全属性(默认安全)
一般可做如下配置:
new Schema({...},{safe:true});
当然我们也可以这样
new Schema({...},{safe:{j:1,w:2,wtimeout:10000}});
j表示做1份日志,w表示做2个副本(尚不明确),超时时间10秒
strict——严格配置(默认启用)
确保Entity
的值存入数据库前会被自动验证,如果你没有充足的理由,请不要停用,例子:
var ThingSchema = new Schema({a:String});
var ThingModel = db.model('Thing',SchemaSchema);
var thing = new Thing({iAmNotInTheThingSchema:true});
thing.save();//iAmNotInTheThingSchema这个属性将无法被存储
如果取消严格选项,iAmNotInTheThingSchema将会被存入数据库
该选项也可以在构造实例时使用,例如:
var ThingModel = db.model('Thing');
var thing1 = new ThingModel(doc,true); //启用严格
var thing2 = new ThingModel(doc,false); //禁用严格
注意:strict
也可以设置为throw
,表示出现问题将会抛出错误
shardKey
需要mongodb
做分布式,才会使用该属性
capped——上限设置
如果有数据库的批量操作,该属性能限制一次操作的量,例如:
new Schema({...},{capped:1024}); //一次操作上线1024条数据
当然该参数也可是JSON
对象,包含size
、max
、autiIndexId
属性
new Schema({...},{capped:{size:1024,max:100,autoIndexId:true}});
versionKey——版本锁
版本锁是Mongoose
默认配置(__v
属性)的,如果你想自己定制,如下:
new Schema({...},{versionKey:'__someElse'});
此时存入数据库的版本锁就不是__v
属性,而是__someElse
,相当于是给版本锁取名字。
具体怎么存入都是由Mongoose
和MongoDB
自己决定,当然,这个属性你也可以去除
new Schema({...},{versionKey:false});
除非你知道你在做什么,并且你知道这样做的后果
更新
有许多方式来更新文件,以下是常用的传统方式:
PersonModel.findById(id,function(err,person){person.name = 'MDragon';person.save(function(err){});
});
这里,利用Model
模型查询到了person
对象,该对象属于Entity
,可以有save
操作,如果使用Model
操作,需注意:
PersonModel.findById(id,function(err,person){person.name = 'MDragon';var _id = person._id; //需要取出主键_iddelete person._id; //再将其删除PersonModel.update({_id:_id},person,function(err){});//此时才能用Model操作,否则报错
});
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, //嵌套Documentchildren2:[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}
});
//其实就是匿名混合模式
Model
什么是Model
Model
模型,是经过Schema
构造来的,除了Schema
定义的数据库骨架以外,还具有数据库行为模型,他相当于管理数据库属性、行为的类
如何创建Model
你必须通过Schema
来创建,如下:
//先创建Schema
var TankSchema = new Schema({name:'String',size:'String'
});
//通过Schema创建Model
var TankModel = mongoose.model('Tank',TankSchema);
操作Model
该模型就能直接拿来操作,具体查看API
,例如:
var tank = {'something',size:'small'};
TankModel.create(tank);
注意: 你可以使用Model
来创建Entity
,Entity
实体是一个特有Model
具体对象,但是他并不具备Model
的方法,只能用自己的方法。
//通过Model创建Entityvar tankEntity = new TankModel('someother','size:big');tankEntity.save();
Query
查询是数据库中运用最多也是最麻烦的地方,这里对Query解读的并不完善,仅仅是自己的一点领悟而已。
查询的方式
通常有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);
Validation
验证始终定义在
SchemaType
中验证是一个内部中间件
验证是在一个
Document
被保存时默认启用的,除非你关闭验证验证是异步递归的,如果你的
SubDoc
验证失败,Document
也将无法保存验证并不关心错误类型,而通过
ValidationError
这个对象可以访问
验证器
required
非空验证min/max
范围验证(边值验证)enum/match
枚举验证/匹配验证validate
自定义验证规则
以下是综合案例:
var PersonSchema = new Schema({name:{type:'String',required:true //姓名非空},age:{type:'Nunmer',min:18, //年龄最小18max:120 //年龄最大120},city:{type:'String',enum:['北京','上海'] //只能是北京、上海人},other:{type:'String',validate:[validator,err] //validator是一个验证函数,err是验证失败的错误信息}
});
验证失败
如果验证失败,则会返回err
信息,err
是一个对象该对象属性如下
err.errors //错误集合(对象)
err.errors.color //错误属性(Schema的color属性)
err.errors.color.message //错误属性信息
err.errors.path //错误属性路径
err.errors.type //错误类型
err.name //错误名称
err.message //错误消息
一旦验证失败,Model
和Entity
都将具有和err
一样的errors
属性
Middleware中间件
什么是中间件
中间件是一种控制函数,类似插件,能控制流程中的init
、validate
、save
、`remove方法
Serial串行
串行使用pre
方法,执行下一个方法使用next
调用
var schema = new Schema(...);
schema.pre('save',function(next){//做点什么next();
});
Parallel并行
并行提供更细粒度的操作
var schema = new Schema(...);
schema.pre('save',function(next,done){//下一个要执行的中间件并行执行next();doAsync(done);
});
中间件特点
一旦定义了中间件,就会在全部中间件执行完后执行其他操作,使用中间件可以雾化模型,避免异步操作的层层迭代嵌套
使用范畴
复杂的验证
删除有主外关联的doc
异步默认
某个特定动作触发异步任务,例如触发自定义事件和通知
例如,可以用来做自定义错误处理
schema.pre('save',function(next){var err = new Eerror('some err');next(err);
});
entity.save(function(err){console.log(err.message); //some err
});
Nodejs+Express学习二(Mongoose基础了解)相关推荐
- OpenStack(M)+ ubuntu 搭建学习二:基础环境配置
目录 一.配置网络接口 二.配置域名解析 三 .配置国内的软件源 四. 启用OpenStack库 五.安装OpenStack客户端 六.同步系统时钟与时钟服务器(NTP) 具体步骤可参考官方文档:Op ...
- MVC学习二:基础语法
目录 一:重载方法的调用 二:数据的传递 三:生成控件 四:显示加载视图 五:强类型视图 六:@Response.Write() 和 @Html.Raw()区别 七:视图中字符串的输入 八:模板页 一 ...
- nodejs express 学习
nodejs的大名好多人应该是听过的,而作为nodejs web 开发的框架express 大家也应该比较熟悉. 记录一下关于express API 的文档: express() 创建express ...
- Windows编程语言VBA学习(二)——VBA基础
通过上一篇文章我们可以了解到VBA的使用方式. 这一篇就介绍一下VBA编程的基本语法 在学习之前需要先声明--VBA仅在Microsoft Office软件上有效,使用WPS的话请去查找JSA语法,避 ...
- s3c2440芯片累加汇编语言,S3C2440学习二(基础资源的使用)
②大概有多少资源模块? ARM处理器共有37个寄存器:1) 31个通用寄存器,包括程序计数器(PC).这些寄存器都是32位的:2)6个状态寄存器.这些寄存器也是32位的,但是只使用了其中的12位. S ...
- Python爬虫学习二爬虫基础了解
1.什么是爬虫 爬虫就是进入网页自动获取数据的程序.当它进入一个网页时,将网页上需要的数据下载下来,并跟踪网页上的其他链接,进入新的页面下载数据,并继续跟踪链接下载数据. 2.URL URL,即统一资 ...
- 开始nodejs+express的学习+实践(8)
为什么80%的码农都做不了架构师?>>> 1.session使用 介绍的非常详细: http://www.cnblogs.com/chenchenluo/p/4197181.h ...
- Nodejs入门学习,nodejs web开发入门,npm、express、socket配置安装、nodejs聊天室开发
一.Node.js:服务器端的 JavaScript 运行环境,它具有无阻塞和事件驱动等特色,采用 V8 引擎,实现了类似 Apache 和 Nginx 的 Web 服务,让你可以通过它来搭建基于 J ...
- Tensorflow深度学习之十二:基础图像处理之二
Tensorflow深度学习之十二:基础图像处理之二 from:https://blog.csdn.net/davincil/article/details/76598474 首先放出原始图像: ...
最新文章
- 针对MySQL数据库主从延迟的问题
- 使用 Chrome DevTools 调试 JavaScript
- linux的/etc/hosts的作用
- 与鬼古女夜晚后海边的“恐怖”聚会
- Node.jsNPM的安装与配置(转)
- 2020快手食品行业数据价值报告
- Python 各种报错解释及处理方法
- Programming Ruby学习笔记一
- 【iOS】使用SQLite3的时候Swift3.、XCode8报错:Unsupported architecture - 'arm/arch.h'file not found - Could not
- pytorch nonzero_[深度学习框架]PyTorch常用代码段
- net.sf.json.JSONException: #39;object#39; is an array. Use JSONArray instead
- Java垃圾回收机制的工作原理
- 投资62笔!私募巨头凯雷的“中国生意经”
- 菜鸟学习oracle一看就会
- 国外量化投资的经典案例
- android开发 节省流量,流量节省程序模式 | Android 开源项目 | Android Open Source Project...
- 插座质检报告办理第三方检测
- 基于bim技术的应用软件有?哪些提高bim工作效率的revit插件
- 【Android SDM660源码分析】- 04 - UEFI ABL LinuxLoader 代码分析
- 掌财社:千亿市值“插座茅”被反垄断调查!高瓴、社保基金踩雷 公司火线回应