Mongoose:Schema之路

說明:本文在我的博客地址爲edwardesire.com,歡迎前來品嚐。javascript


  1. Mongoose學習html

    mongoose的真生

    這裏的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的學習就暫時告一段落,接下來講說項目遇到的問題。


  1. 項目實戰

    前端傳到後臺的內容爲一個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]
     };

    這種相似於俄羅斯套娃結構的方法能解決一部分問題,可是沒法適應真實應用環境。由於決策樹的層數是可大可小的,也沒法預估一個合適的最大值,何況代碼也不美觀。這個問題也一直懸在這裏,但願有大神可以留下聯繫方式和解決方法,予人玫瑰,手留餘香。而項目由於時間關係,估計就只能修改結構來逃避這個問題了。

  2. Next

    選擇的替代方案是將這種樹狀結構變成簡單的數組結構,而後在後端與前端交互時進行樹結構的拼接和拆散。這種方法涉及到樹與二叉樹的轉化以及二叉樹的序列化兩方面知識。好好學習

相關文章
相關標籤/搜索