摘要:學習注定少不了與數據庫打交道,而和可以說是絕配,這篇主要是簡單介紹這個模塊。通過創建查詢是數據庫中運用最多也是最麻煩的地方,這里對解讀的并不完善,僅僅是自己的一點領悟而已。
學習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; //需要取出主鍵_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方法
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 } }); //其實就是匿名混合模式
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創建Entity var 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, //年齡最小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屬性
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 });
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/79748.html
摘要:學習注定少不了與數據庫打交道,而和可以說是絕配,這篇主要是簡單介紹這個模塊。通過創建查詢是數據庫中運用最多也是最麻煩的地方,這里對解讀的并不完善,僅僅是自己的一點領悟而已。 學習Node注定少不了與數據庫打交道,而MongoDB和Node可以說是絕配,這篇主要是簡單介紹Mongoose這個模塊。由于本人也是邊學邊寫的這篇文章,絕對會有新手的味道,請大神看到這里就表往下看了。 名詞介紹稍...
摘要:沒有耐心閱讀的同學,可以直接前往學習全棧最后一公里。我下面會羅列一些,我自己錄制過的一些項目,或者其他的我覺得可以按照這個路線繼續深入學習的項目資源。 showImg(https://segmentfault.com/img/bVMlke?w=833&h=410); 本文技術軟文,閱讀需謹慎,長約 7000 字,通讀需 5 分鐘 大家好,我是 Scott,本文通過提供給大家學習的方法,...
摘要:是的源碼,算是一個基本的博客系統,包含文章發布,關注,評論等功能。這些功能可以說是任何一個網站的基礎。比如運營數據配置和其他數據配置分開,因為很有可能需要做一個小的工具來讓非技術人員配置相關參數。模式在中有一個專門的章節來講解。 1. About 1.1 what: nodeclub是cnodejs.com的源碼,cnode算是一個基本的博客系統,包含文章發布, 關注,評論等功能...
閱讀 3242·2021-10-21 17:50
閱讀 3254·2021-10-08 10:05
閱讀 3380·2021-09-22 15:04
閱讀 581·2019-08-30 14:00
閱讀 1939·2019-08-29 17:01
閱讀 1508·2019-08-29 15:16
閱讀 3219·2019-08-26 13:25
閱讀 852·2019-08-26 11:44