說明:本文在我的博客地址爲edwardesire.com,歡迎前來品嚐。javascript
Mongoose學習html
這裏的Mongoose固然不是圖片上的萌物,它是一個MongoDB對象建模工具(object modeling tool),之前在sails上用的Waterline是ORM (Object Relational Mapper)。當使用Mongoose時,咱們不在須要在數據庫中建立好結構(Schema)以後,再與後端代碼中建立的對象或類進行映射這樣繁瑣的操做。在Mongoose的封裝下,咱們只需定義好JSON類型的數據結構便可。固然我沒有在Nodejs直接使用過MongoDB,不過想一想必定也是很麻煩的。前端
Mongoose的優勢還有不少,我比較籠統地說一下。它實用性在於與數據庫的交互是一種結構化以及可重複的方式,有助於進行一些很廣泛的數據庫任務,也減小了嵌套回調的複雜性。還有的是它不像MongoDB直接返回一個JSON的字符串,而是返回JSON對象。固然,目前Mongoose對於Schema-less data、Random documents、Pure Key-Value pairs是無解的。1.1 connection
第一步固然是鏈接數據庫了。如圖,鏈接數據庫的配置文大體分爲三步。java
第一步是進行鏈接,鏈接字符串 mongodb://<db_user>:<db_password>@<hostname>:<port>/<dbname>
中間必須填寫的部分爲server和hostname,咱們可使用兩種方法來打開數據庫鏈接(mongoose.connect和createConnection):我通常就使用mongoose.connect(db);
,當咱們須要使用多數據庫鏈接時,咱們就須要使用第二種方法了 var connectName = mongoose.createConnection(db#)
;第二步就是輸出運行日誌信息,在成功鏈接、斷開鏈接或者報錯時,監聽相應的事件並在console輸出運行信息;第三步是斷開鏈接,通常的最佳實踐是在程序運行時就打開鏈接,而程序中止或重啓時就須要手動斷開數據庫鏈接。node
1.2 Schema Model
Schema是一個文檔的數據結構,正如我前面提到的,它在Mongoose是一個JSON對象。它最大的特色就是無需肯定字段的大小,這特別適用於須要改變對象大小的狀況。git
它支持8種數據類型(String、Number、Date、Boolean和Buffer、ObjectId、Mixed、ObjectId、Mixed、Array)。Buffer是用來存儲2進制數據,ObjectId是不一樣於_id的特定的識別符。Mixed能夠指定任意類型,不過Mongoose不會自動識別。Array用來存放基本數據類型,也能夠是子文檔。好比github
var childrenSchema = new Schema({ //some structure }); var fatherSchema = new Schema({ //some structure children: [childrenSchema] });
Model是對應Schema的編譯版本,一個model的實例直接映射爲數據庫中的一個文檔。基於這種關係,model處理全部的文檔交互(也就是下文的CRUD)。咱們經過 mongoose.model(modelname, schemaName)
來構建model。這樣一來咱們就能夠一氣呵成地將數據存入數據了。mongodb
var mongoose = require('mongoose'); var Schema = mongoose.Schema; //聲明Schema var nodeSchema = new Schema({ name: String, age: Number }); //構建model mongoose.model('Node', nodeSchema); //簡單的數據交互 //建立兩個實例 var node = new Node({name: 'Edward', age: '23'}); node.save(function(err){ if(err){ console.log(err); }else{ console.log('The new node is saved'); } });
1.3 CRUD
咱們把Create、Read、Update、Delete操做一塊兒稱呼爲CRUD,這4個操做是持久性存儲的基本操做。在Mongoose中的模型方法(Model methods)對應的就有有Model.create(),Model.find(),Model.update(),Model.remove()方法,實例方法也是同樣的,不過他做用於特定的實例罷了。數據庫
1.3.1 Create Datajson
首先是建立數據的模型方法 Model.create()
,此方法直接將數據存入數據庫。
Node.create({name: 'Edward', age: '23'}, function(err, node, numAffected){ if(err){ res.send({'success':false,'err':err}); }else{ res.send({'success':true}); console.log("node created and saved: " + node); res.redirect('/'); } });
而實例方法就是在建立實例就將數據以JSON對象傳遞給實例(如上一節的例子),固然咱們也能夠在實例創造以後再添加數據。
var node = new Node(); node.age = 23;
可是這都只是保存在了應用,咱們須要使用instance.save()保存。一步到位的寫法以下。
var node = new Node({name: 'Edward', age: '23'}).save(function(err){ if(err){ console.log(err); }else{ console.log('The new node is saved'); } });
1.3.2 Read Data
讀取數據,模型方法有3種:Model.find()---找到全部符合添加的文檔並返回一個表單, Model.findOne()---返回首先找到的單個文檔,Model.findById()---經過ID(惟一)來查找。這3屬於靜態方法,咱們也能夠建立本身的靜態方法。好比經過文檔中的某個鍵來查找數據。
Dtree.findByName(req.params.name, function(err, dtree){ if(!err){ //do something }else{ console.log('Somthing wrong: ' + err); } });這些方法的完整參數爲
Model.find(conditions, [fields], [options], [callback])
,可選項fields爲指定返回的值,options爲指定序列等。具體的細節能夠看文檔MongooseAPI。須要注意的是,若是不定義回調函數的話,須要使用.exec()來顯性調用更新函數。1.3.3 Update Data
更新數據一樣有3個靜態模型方法:Model.update(),Model.findOneAndUpdate(),Model.findByIdAndUpdate()。他們的參數都有4個(conditions, update, ooptions,callback)。一樣在文檔MongooseAPI中能夠查詢到。
可是這3種方法都沒法使用一些自定義的運行機制。而這有一套標準作法:find-edit-save方法。咱們來看看例子。
//1.查找記錄 Dtree.findByName(req.params.name, function(err, dtree){ if(!err){ //成功讀取dtree //讀取JSON文件,得到須要添加的內容 var json; fs.readFile('./public/javascripts/update.json', 'utf8', function (err, data) { if(err) throw err; json = JSON.parse(data); //2.修改dtree記錄,將json插入到structure dtree.structure.push(json); //3.保存記錄到數據庫 dtree.save(function(err, tree){ if(err){ console.log('Somthing wrong: ' + err); }else{ console.log('Add a new node', tree); res.redirect('/dtree/json/Type00'); } }); }); }else{ console.log('Somthing wrong: ' + err); } });
1.3.4 Delete Data
刪除數據一樣須要查找到數據再刪除:Model.remove(),Model.findOneAndRemove(),Model.findByIdAndRemove()。.remvoe()的參數就是可選擇的callback,後面兩個還多了一個option參數,具體可查詢Mongoose API。而.remove()方法能夠做爲模型方法調用,也能夠做爲實例方法調用。
//Model method Node.remove({name: 'Edward'} function(err){ if(!err){ //成功刪除全部name爲Edward的文檔 } }); //Instance method Node.findOne({name: 'Edward'}, function(err, node){ if(!err){ node.remove(function(err){ //成功刪除首位name爲Edward的文檔 }); } });
好了,Mongoose的學習就暫時告一段落,接下來講說項目遇到的問題。
項目實戰
前端傳到後臺的內容爲一個json結構的決策樹,大體的結構以下。分爲三大部分:config,parameter,structure。
前面兩部分相對比較容易解決,最大的問題是structure中有個children子節點,而子節點還會增長新的子節點,具體層級也是隨着問題變化的。在個人初版Schema中很天真地這樣定義。
var dtreeSchema = new Schema{ //其餘數據結構 structure: [chilldrenSchema] }; var chilldrenSchema = new Schema{ //其餘數據結構 children: [chilldrenSchema] };
程序的控制是這樣寫的
//params req.params.name exports.createDtreeChildren = function(req, res){ //Find dtree by name Dtree.findByName(req.params.name, function(err, dtree){ if(!err){ //成功讀取tree //讀取新增結點 var json; fs.readFile('./public/javascripts/update.json', 'utf8', function (err, data) { if(err)throw err; json = JSON.parse(data); //structure parse //structure 爲一個數組 //structure[i] 爲首個結點 //structure[i].children 爲其子節點 var newchildren = dtree.structure[0].children; //2.插入structure dtree.structure[0].children.push(json); console.log(dtree.structure[0].children); dtree.markModified(dtree.structure[0].children); //3.save to mongodb dtree.save(function(err, tree){ if(err){ console.log('Somthing wrong: ' + err); }else{ console.log('Add a new node: '+ dtree.structure[0].children); res.redirect('/dtree/json/Type00'); } }); }); }else{ console.log('Somthing wrong: ' + err); } }); };
這樣會出現一個問題,那就是輸出(dtree.structure[0].children)的是正確修改後的數據,而卻沒有正確存入數據庫。其中的緣由是Mogoose對於結構的聲明是有嚴格順序的(Order of schema declarations)。一樣的,我在第二次修改後,chilldrenSchema寫到了dtreeSchema的前面,chilldrenSchema本身的children的[chilldrenSchema]類型如指望同樣沒法存入數據庫,Mongoose把undefined(具體是[undefined]仍是undefined我不肯定)。我想到了一個十分醜陋的解決方法就是手動地添加足夠大的層數。
var ninethChilldrenSchema = new Schema{ //其餘數據結構 //children: [nextLaryerChilldrenSchema] }; var eighthChilldrenSchema = new Schema{ //其餘數據結構 children: [ninethChilldrenSchema] }; //中間依次類推到底 var chilldrenSchema = new Schema{ //其餘數據結構 children: [secondChilldrenSchema] }; var dtreeSchema = new Schema{ //其餘數據結構 structure: [chilldrenSchema] };
這種相似於俄羅斯套娃結構的方法能解決一部分問題,可是沒法適應真實應用環境。由於決策樹的層數是可大可小的,也沒法預估一個合適的最大值,何況代碼也不美觀。這個問題也一直懸在這裏,但願有大神可以留下聯繫方式和解決方法,予人玫瑰,手留餘香。而項目由於時間關係,估計就只能修改結構來逃避這個問題了。
Next
選擇的替代方案是將這種樹狀結構變成簡單的數組結構,而後在後端與前端交互時進行樹結構的拼接和拆散。這種方法涉及到樹與二叉樹的轉化以及二叉樹的序列化兩方面知識。好好學習