前言:本學習參考文檔僅供參考,若有問題,師請雅正前端
Schema
: 一種以文件形式存儲的數據庫模型骨架,不具有數據庫的操做能力node
Model
: 由Schema
發佈生成的模型,具備抽象屬性和行爲的數據庫操做對mongodb
Entity
: 由Model
建立的實體,他的操做也會影響數據庫數據庫
注意:編程
1.本學習文檔採用嚴格命名方式來區別不一樣對象,例如:數組
var PersonSchema; //Person的文本屬性 var PersonModel; //Person的數據庫模型 var PersonEntity; //Person實體
2.Schema
、Model
、Entity
的關係請牢記,Schema
生成Model
,Model
創造Entity
,Model
和Entity
均可對數據庫操做形成影響,但Model
比Entity
更具操做性。安全
2.在項目只可以建立一個數據庫鏈接,以下:app
var mongoose = require('mongoose'); //引用mongoose模塊 var db = mongoose.createConnection('localhost','test'); //建立一個數據庫鏈接
3.打開本機localhost
的test
數據庫時,咱們能夠監測是否有異常異步
db.on('error',console.error.bind(console,'鏈接錯誤:')); db.once('open',function(){ //一次打開記錄 });
注意:
成功開啓數據庫後,就能夠執行數據庫相應操做,假設如下代碼都在回調中處理
4.定義一個Schema
var PersonSchema = new mongoose.Schema({ name:String //定義一個屬性name,類型爲String });
5.將該Schema
發佈爲Model
var PersonModel = db.model('Person',PersonSchema); //若是該Model已經發布,則能夠直接經過名字索引到,以下: //var PersonModel = db.model('Person'); //若是沒有發佈,上一段代碼將會異常
6.用Model
建立Entity
var personEntity = new PersonModel({name:'Krouky'}); //打印這個實體的名字看看 console.log(personEntity.name); //Krouky
7.咱們甚至能夠爲此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
8.Entity
是具備具體的數據庫操做CRUD
的
personEntity.save(); //執行完成後,數據庫就有該數據了
9.若是要執行查詢,須要依賴Model
,固然Entity
也是能夠作到的
PersonModel.find(function(err,persons){ //查詢到的全部person });
注意:
1. 具體的如何配置Schema
、Model
以及Model
和Entity
的相關操做,咱們會在後面進行
2. Model
和Entity
都有能影響數據庫的操做,但仍有區別,後面咱們也會作解釋
若是您還不清楚Mongoose
是如何工做的,請參看第一章快速通道快速瀏覽他的用法吧
Schema
僅僅只是一斷代碼,他書寫完成後程序依然沒法使用,更沒法通往數據庫端var BlogSchema = new Schema({ title:String, author:String //new Schema()中傳入一個JSON對象,該對象形如 xxx:yyyy , /xxx是一個字符串,定義了屬性,yyy是一個Schema.Type,定義了屬性類型 });
Schema.Type
是由Mongoose
內定的一些數據類型,基本數據類型都在其中,他也內置了一些Mongoose
特有的Schema.Type
。固然,你也能夠自定義Schema.Type
,只有知足Schema.Type
的類型才能定義在Schema
內。
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, } });
Buffer
和ArrayBuffer
是Nodejs
兩種隱藏的對象,相關內容請查看NodeJS-API
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();
主鍵,一種特殊並且很是重要的類型,每一個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
在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 PersonModel({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
通常可作以下配置:
new Schema({...},{safe:true});
固然咱們也能夠這樣
new Schema({...},{safe:{j:1,w:2,wtimeout:10000}});
j
表示作1份日誌,w
表示作2個副本(尚不明確),超時時間10秒
確保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
,表示出現問題將會拋出錯誤
須要mongodb
作分佈式,纔會使用該屬性
若是有數據庫的批量操做,該屬性能限制一次操做的量,例如:
new Schema({...},{capped:1024}); //一次操做上線1024條數據
固然該參數也但是JSON對象,包含size、max、autiIndexId屬性
new Schema({...},{capped:{size:1024,max:100,autoIndexId:true}});
版本鎖是Mongoose
默認配置(__v屬性)的,若是你想本身定製,以下:
new Schema({...},{versionKey:'__someElse'});
此時存入數據庫的版本鎖就不是__v
屬性,而是__someElse
,至關因而給版本鎖取名字。
具體怎麼存入都是由Mongoose
和MongoDB
本身決定,固然,這個屬性你也能夠去除
new Schema({...},{versionKey:false});
除非你知道你在作什麼,而且你知道這樣作的後果
該內容將在索引章節單獨講解
Document
是與MongoDB
文檔一一對應的模型,Document
可等同於Entity
,具備屬性和操做性
注意:
Document
的`CRUD都必須通過嚴格驗證的,參看2.5.2 Schema的strict嚴格配置
查詢內容過多,專題講解
有許多方式來更新文件,如下是經常使用的傳統方式:
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; //須要取出主鍵_id delete 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
方法
如同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 } }); //其實就是匿名混合模式
Model
模型,是通過Schema
構造來的,除了Schema
定義的數據庫骨架之外,還具備數據庫行爲模型,他至關於管理數據庫屬性、行爲的類
你必須經過Schema
來建立,以下:
//先建立Schema var TankSchema = new Schema({ name:'String', size:'String' }); //經過Schema建立Model var TankModel = mongoose.model('Tank',TankSchema);
該模型就能直接拿來操做,具體查看API
,例如:
var tank = {'something',size:'small'}; TankModel.create(tank);
注意:
你可使用Model
來建立Entity
,Entity
實體是一個特有Model
具體對象,可是他並不具有Model
的方法,只能用本身的方法。
//經過Model建立Entity var tankEntity = new TankModel('someother','size:big'); tankEntity.save();
查詢是數據庫中運用最多也是最麻煩的地方,這裏對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);
數據的存儲是須要驗證的,不是什麼數據都能往數據庫裏丟或者顯示到客戶端的,數據的驗證須要記住如下規則:
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, //年齡最小18 max: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
屬性
中間件是一種控制函數,相似插件,能控制流程中的init
、validate、
save、
remove`方法
中間件分爲兩類
串行使用pre
方法,執行下一個方法使用next
調用
var schema = new Schema(...); schema.pre('save',function(next){ //作點什麼 next(); });
並行提供更細粒度的操做
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 });