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

    选择的替代方案是将这种树状结构变成简单的数组结构,而后在后端与前端交互时进行树结构的拼接和拆散。这种方法涉及到树与二叉树的转化以及二叉树的序列化两方面知识。好好学习

相关文章
相关标签/搜索