从MongoDB入门到编写数据库相关API

这篇文章为实验室开会本人讲解mongoDB的讲义,主要讲述两个内容:MongoDB的入门和Nodejs编写相关API入门。内容比较简单,但是实践性比较大,最终的目的是实验室的师弟师妹们能够通过编写Node将‘从数据库增删查改数据’——‘提供给前端调用的API’这个后端工作流实现。内容有些欠缺地方,还需大家指点。


  1. 前期准备——MongoDB简介(原文出处

    mongodb 这个名词相信大家不会陌生吧。有段时间 nosql 的概念炒得特别火,其中hbase、redis、mongodb、couchdb之类的名词都相继进入了大众的视野。 虽然这四种数据库都属于 nosql 的大范畴。但它们关注的领域是不一样的。hbase 是存海量数据的,redis 用来做缓存,而 mongodb 和 couchdb 则试图取代一些使用 mysql 的场景。

    再说一下MongoDB的特点: 开源文档型nosql

    开源的意思就是Mongo的代码是自由提供给开发者,我们可以在官网点击github上的mongo项目(如下图)。

    文档型是个需要理解的重要概念。

    在 sql 中,我们的数据层级是:数据库(db) -> 表(table) -> 记录(record)-> 字段。

    在 mongodb 中,数据的层级是:数据库 -> collection -> document -> 字段。这四个概念可以对应得上。

    文档型数据这个名字中,“文档”两个字很容易误解。其实这个文档就是 bson 的意思。bson 是 json 的超集,比如 json 中没法储存二进制类型,而 bson 拓展了类型,提供了二进制支持。mongodb 中存储的一条条记录都可以用 bson 来表示。所以你也可以认为,mongodb 是个存 bson 数据的数据库,或是存哈希数据的数据库。

    mongodb 相对于它的竞争对手们来说——比如 couchdb,它的一大优势就是尽可能提供与 sql 对应的概念。之前说了,sql 中的记录对应 mongodb 中的 document,记录这东西是一维的,而 document 可以嵌套很多层。在某些场景下,比如存储一个文章的 tags,mongodb 中的字段可以轻松存储数组类型,而 sql 中就需要设计个一对多的表关系出来。

    假设有一个 blog 应用,其中有张 Post 表,表中有用户发表的一些博客内容(post)。

    nosql是相对于sql数据库的一个概念,全称是Not Only SQL的意思,NoSQL通过弱化事务和多表关联的功能,牺牲一致性,增强分区的扩展性,对于mongoDB来说水平扩展就是auto-sharding的功能。这一理论就是BASE(基本可用性、软状态与最终一致性)的含义——“NoSQL数据库设计可以通过牺牲一定的数据一致性和容错性来换取高性能”。

    其他的理论知识可以看《MongoDB理论浅入浅出》。mongodb 的官网中有一些特性介绍:其中标有箭头的是基本概念,圆圈的是进阶概念,画叉的不必了解。

    反正使用 mongodb 时,一定要思考的两点就是:表join(联立)到底要不要,事务支持到底要不要。

  2. 前期准备——mongodb等软件的安装(可选)

    我们实验室一般开发环境为Windows,部署环境为Linux。在这里,我以在Windows安装开发环境为讲解内容。

    步骤分为:安装软件->安装服务->管理工具连接。具体内容可看往期文章以及我发给大家的邮件。

    trouble shooting:

    • Win8在安装mongoDB时报错的解决方法:这是由于安装msi文件的问题。我们首先在Windos的菜单搜索cmd,右键管理员运行,进入命令行窗口。输入如下命令 'msiexec /i 系统盘:\msi文件名.msi' 。其中'系统盘:\msi文件名.msi'为MongoDB安装程序位置。

    • 安装MongoDB服务时权限问题:安装Windows服务需要管理员权限,参照此文章安装Windos服务。并注意如下几个步骤:在Windos的菜单搜索cmd,右键管理员运行;在mongod.cfg输入的数据库文件夹需要先建立对应的文件夹。这样就不会报安装服务的错误。

    • npm install安装失败:从git上clone代码时需要安装依赖包,但是基于网络访问环境以及本地vs c++环境可能会导致安装失败。大家可以从百度云中的nodemodules压缩包解压到应用根目录(也就是项目的nodejsserver文件夹),当然最好的安装方法还是去排错使得npm install能够安装成功。

  3. MongoDB的CRUD基本操作

    • 第一步:连接数据库

      打开robomongo软件,进行五步操作。

      如果连接成功后,软件没有出现如下页面,则在菜单栏的View菜单下点击Explorer将页面显示出来。

    • 第二步:创建数据库以及集合

      数据库连接成功后,我们按照‘数据库’->‘集合’->‘文档’的顺序建立实例。在数据库连接中右键点击'Create Database',创建有意义的数据库名字,比如我们的这个项目为'ExpressSecurity'。

      右键点击数据库中的'Create Collection'创建集合并命名,比如营业中心采集的快递单'ordersFromOperating'。

    • 第三步:插入数据(create操作)

      右键点击要给集合,并选择'insert document',输入下图插入文档窗口中的数据内容。

      双击'ordersFromOperating',即可在右侧看到该集合刚才插入的数据。

      当然我们可以直接在MongoDB shell中调用MongoDB API的.insert()进行插入数据操作。我们还可以调用.find()来查询,.replace()来删除,.update()来更新。因为GUI操作更加直观所以下面的操作使用shell命令进行。

    • 第四步:更新操作(update操作)

      通过.update()来更新数据,第一个参数是更新的目标文档满足条件,第二个参数是相应的修改字段,第三个参数没有写,是options,来指明是否同时更新多个满足条件的文档之类的设置。我们这里就直接粗犷地不设置options,采用默认的配置,只更新第一个满足条件的文档。

    • 第五步:查询操作(read操作)

      当需要查询数据时,我们使用.find()来获得文档数据。当我们想获取一个文档对象时,则需要使用findOne()来查询文档。

    • 第六步操作:删除操作(delete操作)

      删除文档的参数也是满足条件的文档被删除,不赘述。

    以上就是数据库层次的CRUD操作,下面进入最后一个环节——node脚本的CRUD操作。

  4. 服务器脚本

    关于Nodejs相关内容上次已经讲述过,这里再简介一下。首先,Nodejs在这个平台的作用就是服务器环境+服务器脚本语言(类比nginx+php)。其次,我们现在使用的技术栈——MEAN中的Express为Node的服务器MVC框架,类比到php中就是Code Ingiter、Drupal这种的框架。我们先看下Express的文件目录(下面3张图依次从顶层目录结构到config、app两个后端比较重要的目录结构)。

    根目录下的服务器的入口文件server.js在运行服务器命令中的执行文件。通过将不同功能服务(express、passport、socket.io)模块绑定到server上。而这些服务都在config目录中配置。

    config目录我们接触的就是端口、数据库地址的修改,一般设置好就不需要再配置。下面的app目录才是的核心,我们先粗略地介绍一下功能分区。在后面的实际编码中具体讲解如何编写代码。

    我们首先在models目录中建模,这里就要引入mongoose模块。mongoose 是个ODM。ODM的概念对应SQL数据库中的ORM(ORM 全称是Object-Relational Mapping,对象关系映射;而ODM是Object-Document Mapping,对象文档映射)。 它的作用就是,在程序代码中,定义一下数据库中的数据格式,然后取数据时通过它们,可以把数据库中的 文档 映射成程序中的一个对象,这个对象有 .save .update 等一系列方法,和 .title .author 等一系列属性。在调用这些方法时,odm 会根据你调用时所用的条件,自动转换成相应的 mongodb shell 语句帮你发送出去。自然地,在写代码时通过链式调用一个个的方法要比手写数据库操作语句具有更大的灵活性和便利性。这里需要注意的是Mongoose默认将数据存储到MongoDB的集合是schema名小写加s。

    那么我们首先将目标数据结构在mongoose中建模,我们在models目录中新建文件:ordersFromOperating.server.model.js。

    'use strict';
    var mongoose = require('mongoose'),
    Schema = mongoose.Schema;
    /**
     * ordersFromOperating Schema
     * 集合的数据格式定义类型和字段名
     */
    var Orders_from_operating_schema = new Schema({
        deptID: { //快递单位编号
            type: String
        },
        expressID: { //快递单号
            type: String
        },
        senderPhone: { //寄件人手机号
            type: String
        },
        senderID: { //寄件人身份证号
            type: String
        },
        photo: { //物品拍照
            type: String
        },
        recipientPhone: { //收件人手机号
            type: String
        },
        courierId: { //快递员工号
            type: String
        }
    });
    //model化
    mongoose.model('Orders_from_operating', Orders_from_operating_schema);
    

    同样地,建立模型ordersFromPicking。

    第二步就是在控制器目录中使用mongoose的API操作数据流。在controllers目录下新建orders.server.controller.js,我们将在这个文件bain写读取数据库的代码。

    /**
     * Module dependencies.
     */
    var _ = require('lodash');
    var mongoose = require('mongoose');
    var async = require('async');
    var ObjectId = mongoose.Types.ObjectId;
    var Orders_from_operating = mongoose.model('Orders_from_operating');
    var Orders_from_picking = mongoose.model('Orders_from_picking');
    /**
     * 响应GET /api/orders/:expressId 获得expressId邮件
     * @param {Object} req GET查询mailId参数通过req.params传递
     * @param {Object} res 提供服务器响应对象
     */
    exports.getOrderByExpressId = function(req, res) {
      //获得查询条件
      var expressId = req.params.expressId;
      //获取两个表的数据
      async.parallel({
        fromOperating: function(callback){
          var query = Orders_from_operating.findOne({ 'expressID': expressId })
                      .select('deptID expressID senderPhone senderID photo recipientPhone courierId')
                      .lean();
          //执行查询操作
          query.exec(function(err, orders){
            if(err){
              callback(err);
            }else{
              callback(null, orders);
            }
          });
        },
        fromPicking: function(callback){
          var query = Orders_from_picking.findOne({ 'expressID': expressId })
                      .select('x-rayPhoto')
                      .lean();
    
    
    
      //执行查询操作
      query.exec(function(err, orders){
        if(err){
          callback(err);
        }else{
          callback(null, orders);
        }
      });
    }
    
    }, function(err, results) { var order = {}; if(err){ console.log('ERR: find order failed: ' + err); res.status(500).json(err); }else{ order = _.assign(results.fromOperating, results.fromPicking); res.status(200).json(order); } }); };

    最后就是新建对应的路由,在routes目录中新建orders.server.routes.js路由文件。

    'use strict';
    module.exports = function(app) {
        // Root routing
        var orders = require('../../app/controllers/orders.server.controller');
        app.route('/api/orders/:expressId').get(orders.getOrderByExpressId);
    };
    

    更多的async操作可看《使用Async进行流程控制》。 我们通过PostMan工具来发送HTTP请求得到的测试结果如下图。

    这个就是快递安检平台的最主要的查询操作。其实我们也知道RESTful API包含CRUD这4个操作,其他3个操作就留给大家做吧~~拜拜


参考:

  1. Node.js 包教不包会@github

  2. mongoose API@官网