背景

后台管理系统需要数据存储,之前自己偷懒以为只要内存就可以,但是在服务器托管需要pm2来进行管理,守护多个进程常常失效,因此研究一下数据库连接。

相关调研

Node.js 应用一般有三种方式保存数据。

  • 不使用任何数据库管理系统(DBMS),把数据保存在内存里或直接使用文件系统。
  • 使用关系数据库。例如 MySQL, PostgreSQL.
  • 使用非关系数据库。例如 RedisMongoDBCouchDB, PouchDB.

无服务器数据存储

从管理上来说,第一种方式是最方便易用的。不需要安装任何数据库,直接使用内存和文件就行了。

无需数据库的内存存储就是使用变量保存程序的计算结果或者状态。简单来说就是程序的变量。这种方式适合存储一些小的使用频率很高的数据,因为它的存取速度非常快。但是一旦程序重启或者服务器重启,这些数据就会丢失。

文件存储就是直接使用服务器的文件系统来保存数据。通常应用程序的配置信息都是使用这种方式。程序或服务器重启都不会影响到这些数据。但是这种方式有并行的问题。比如一个多用户的系统,两个用户同时读写一个文件就会造成一个用户的数据被另外一个覆盖。这个时候就应该使用数据库来存储数据了,数据库可以更好地处理并行的问题。

关系数据库 (RDBMS)

关系数据库适合复杂信息的存储和查询。很多内容管理(CMS),客户关系管理(CRM)和网络商店使用这类数据库。

不过这类数据库需要专门的管理知识。一般都要安装在单独的服务器上,由专人(DBA)来管理。开发人员还需要懂得 SQL 语言。虽然有开发库提供关系对象映射(ORM))的 API 供开发人员调用,不用直接写 SQL 语句,但是最好还是得懂 SQL。

非关系数据库(NoSQL Database)

其实在最早期一直是非关系数据库的天下,后来关系数据库逐渐流行开来变为主流。不过最近非关系数据库因为简单和可扩展性强又开始受欢迎。风水轮流转啊。

关系数据库牺牲了性能换来了可靠性。许多非关系数据库一般是把性能作为首位,所以非关系数据库可能更适合用于实时分析或即时消息的应用场景。另外,非关系数据库通常不要求事先定义数据的 schema,因此更为灵活,特别适合保存层级 (hierarchy) 变化的数据,所以和 JSON 特别匹配。而且非关系数据库的数据结构更类似程序里经常使用的数据结构,比如哈希表,链表和 键值对(key/value pair)等等,对开发更友好。

小结

其实总结来说,关系数据库适合保存一些长效数据,非关系数据库适合保存一些短时数据。相互结合。

MongoDB 保存页面等数据
MySQL 保存订单与用户数据
复制代码

环境

安装MongoDB

Mac环境:

# 进入 /usr/local
cd /usr/local

# 下载
sudo curl -O https://fastdl.mongodb.org/osx/mongodb-osx-x86_64-3.4.2.tgz

# 解压
sudo tar -zxvf mongodb-osx-x86_64-3.4.2.tgz

# 重命名为 mongodb 目录

sudo mv mongodb-osx-x86_64-3.4.2 mongodb
复制代码

将安装目录添加到Path中

export PATH=/usr/local/mongodb/bin:$PATH
复制代码

运行MongoDB

建立一个数据库存储目录

sudo mkdir -p /data/db
sudo mongod
复制代码

需要常开mongodb服务器,保持数据联通。

node端运行

需要安装Node驱动库:

npm install mongodb
复制代码

在项目中,引入客户端:

var MongoClient = require('mongodb').MongoClient;
var assert = require('assert');
var url = 'mongodb://localhost:27017/test';
// 连接数据库并执行回调
MongoClient.connect(url, function(err, db) {
  assert.equal(null, err);
  console.log('Connected correctly to server.');
  db.close();
});
复制代码

连接成功之后,基本上就是数据操作相关的东西了。到这里,mongoDB已经处在了可用的阶段。

概念相关

在这里有这么几个概念需要描述一下,mongodb中基本的概念是文档、集合、数据库,相比于SQL的对照更好理解一些。

  • collection:相当于table的概念,标识一个数据库的集合。
  • document:相当于row,表中的每一行。
  • field:某一个具体的数据库字段。

MongoDB将数据存储为一个文档,数据结构由键值(key=>value)来组成。其中文档相当于JSON对象,字段值可以包含其他文档。

个人感觉MongoDB的一大优势,就是不用过分关注于表和库的建立,将所有的经历可以放在数据存储上面。下面结合一个应用场景,我们来分析一下文档的建立过程。

实践

项目背景

现在要实现一个用户的增删改查,原本的增删改查如下定义:

// 获取账号列表
function accountList(ctx, next) {
    let list = config.accountList;
    return list;
}
// 创建账户信息
function addAccount(ctx, next) {
    let data = ctx.request.body;
    let account = {
        userName: data.userName||'',
        password: data.password||'',
        menus: data.menus || []
    };
    ...
    config.accountList.push(account);
}

// 编辑账户信息
function editAccount(ctx, next) {
    let data = ctx.request.body;
    let account = {
        userName: data.userName||'',
        password: data.password||'',
        menus: data.menus || []
    };
    for(let item of config.accountList){
        if(item.userName === account.userName) {
            item.password = account.password;
            item.menus = account.menus;
        }
    }
}

// 删除账户信息
async function delAccount(ctx, next) {
    let data = ctx.request.body;
    let account = {
        userName: data.userName||'',
        password: data.password||'',
        menus: data.menus || []
    };
    let delIndex;
    config.accountList.forEach((item, index)=>{
        if(account.userName === item.userName) {
            delIndex = index;
        }
    })
    config.accountList.splice(delIndex, 1);
}
复制代码

插入文档

插入文档比较简单,指定对应的collection,进行JSON数据的插入:

var item = {
    userName: '小明',
    password: 'password',
    menus: [
        {
            index: 'account',
            title: '临时用户',
        },
        {
            index: 'hotPage',
            title: '发现页管理',
        }
    ]
}
var insertDocument = function(db, callback) {
   db.collection('account').insertOne( item, function(err, result) {
    assert.equal(err, null);
    console.log("Inserted a document into the account collection.");
    callback();
  });
};
// 下面统一都是在connect回调中调用相关方法,不在赘述
MongoClient.connect(url, function(err, db) {
  assert.equal(null, err);
  insertDocument(db, function() {
    db.close();
  });
});

复制代码

查询文档

查询文档需要使用find方法,find接收两个参数,db.collection.find(query, projection)。其中query为指定的查询条件,如果不指定则返回所有的doc。

如果需要以易读的方式来读取数据,可以使用 pretty() 方法,语法格式如下:

db.col.find().pretty()
复制代码

其中query中还可以跟很多相关的筛选条件,相当于where的部分。

// 查找之后,返回cursor实例,利用each来对每个doc进行处理。
var findAccounts = function(db, callback) {
   var cursor =db.collection('account').find( );
   cursor.each(function(err, doc) {
      assert.equal(err, null);
      if (doc !== null) {
        console.dir(doc);
      } else {
        callback();
      }
   });
};
复制代码

更新文档

更新文档和插入比较类似,利用updateOne方法返回更新文档的数目。接受的参数为queryupdate两个。query为相应的查询条件,update为更新的对象和一些操作符。更新的内容基本在update的set域中。

直接看代码:

var updateRestaurants = function(db, callback) {
   db.collection('accounts').updateOne(
      { "userName" : "小明" },
      {
        $set: { "password": "newpassword" },
        $currentDate: { "lastModified": true }
      }, function(err, results) {
      console.log(results);
      callback();
   });
};
复制代码

对应的,还有saveinsertupdateMany等方法实现更新操作。

删除文档

MongoDB通过remove方法来移除集合中的数据。同update操作一样,需要先对相关的doc进行定位,然后进行删除操作。

官方更推荐使用deleteManydeleteOne方法。

db.collection('restaurants').deleteOne(
      { "userName": "小明" },
      function(err, results) {
         console.log(results);
         callback();
      }
   );
复制代码

结合项目

结合项目,用MongoDB进行改造,以更新一条数据为例:

// db.js 负责和数据库的数据交互

// 获取所有账户列表
async function getaccountlist() {
    const db = await MongoClient.connect(url);
    const collection = db.collection('tempSave');
    const result = await collection.find().toArray();
    console.log(result);
    return result;
}
// 更新一个用户列表
async function updateaccount(item) {
    const db = await MongoClient.connect(url);
    const collection = db.collection('tempSave');
    await collection.updateOne({userName: item.userName},
    {$set:{password: item.password, menus: item.menus}});
}
module.exports = {
    getaccountlist,
    updateaccount
};
复制代码

利用async和await对异步请求进行改造,进行数据的返回。

在项目中,对原有的操作进行修改:

// 编辑账户信息
async function editAccount(ctx, next) {
    let data = ctx.request.body;
    let account = {
        userName: data.userName||'',
        password: data.password||'',
        menus: data.menus || []
    };
    let flag = false;
    let accountList = await getaccountlist();
    // 对数据进行遍历,可以改成更高效的find形式
    for(let item of accountList){
        if(item.userName === account.userName) {
            flag = true;
            await updateaccount(account);
        }
    }
    if(flag) {
        ctx.body = {
            data: '编辑成功',
            code: 0
        }
    }else {
        ctx.body = {
            msg: '未查找到用户',
            code: 1
        }
    }
    await next();
}
复制代码

至此,便完成了对MongoDB的使用。XD