Node 04 使用MongoDB

Node 04 使用MongoDB

上一节结束的时候就说还差数据库了, MongoDB和Node.js发行于同一年, 所以Node应用经常使用MongoDB. NoSQL数据库与SQL数据库的对应关系 在Node中使用MongoDB 新建记录 ObjectID 查找记录 Update Delete NoSQL数据库与SQL数据库的对应

上一节结束的时候就说还差数据库了, MongoDB和Node.js发行于同一年, 所以Node应用经常使用MongoDB.
  1. NoSQL数据库与SQL数据库的对应关系
  2. 在Node中使用MongoDB
  3. 新建记录
  4. ObjectID
  5. 查找记录
  6. Update
  7. Delete

NoSQL数据库与SQL数据库的对应关系

MongoDB用过, 但是没有好好看过. 这次就来结合Node一起用用, 这样Node后端基本就完整了. SQL与NoSQL的对应关系如下:
  1. Table - Collection
  2. Row/Record - Document
  3. Column - Field
MongoDB的官网在https://www.mongodb.com/, 点击其中的Software - MongoDB Community Server, 这个是免费的版本, 然后下载安装, 先来安装CentOs版本, 选择旁边的4.4.0, CentOS 7+, 然后是tgz版本. 依照官网的安装指南来进行安装:
  1. /etc/yum.repos.d/下创建mongodb-org-4.4.repo,内容如下:
        [mongodb-org-4.4]
        name=MongoDB Repository
        baseurl=https://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/4.4/x86_64/
        gpgcheck=1
        enabled=1
        gpgkey=https://www.mongodb.org/static/pgp/server-4.4.asc
    
  2. 然后使用yum直接安装 sudo yum install -y mongodb-org
安装并启动成功之后, MongoDB默认绑定127.0.0.1, 即只能在本机访问, 在配置文件/etc/mongod.conf中修改如下:bindIp: 0.0.0.0, 这样就是绑定了所有IPv4和IPv6地址, 之后使用systemctl restart mongod即可. 和MySQL安装有相似之处. 启动、停止和检测状态在CentOS 7中如下:
  1. systemctl start mongod
  2. systemctl stop mongod
  3. systemctl status mongod
由于mongodb默认不需要用户名和密码就能访问, 所以如果在公网上公开mongodb的端口,小心会被攻击. 不过个人测试用, 也可以了.下边就来看看Node.js中使用MongoDB的驱动然后使用. 想通过图形化操作的话, MongoDB官方有图形化工具, 还有第三方工具robomongo.org, 这个就略过了, 反正用程序操作和图形化没有关系.

在Node中使用MongoDB

和很多数据库一样, 首先还得装驱动. MongoDB官方提供了用于Node.js的驱动, 位于官网-doc-drivers中. 点击其中的Node.js, 再点击API Document就可以看详细情况, 官方驱动在npm中的包名是mongodb. 先安装之. 写这篇博客的时候驱动是3.6版本. 有了驱动, 就可以来搞一下连接, 连接之后就可以看CRUD了.
const mongodb = require("mongodb");
//客户端
const mongoClient = mongodb.MongoClient;

//数据库的URL
const url = 'mongodb://106.54.215.164:27017';

//使用的数据库, 可以自定义
const databaseName = 'weathers';

//三个参数, 第一个是URL, 第二个是配置, 第三个是成功回调函数
mongoClient.connect(url, {useNewUrlParser: true}, function (error, client) {
    if (error) {
        console.log("AN error");
    }
    console.log(url, "Connected.");
});
连接套路就是这个, 然后有个提示:
(node:14928) DeprecationWarning: current Server Discovery and Monitoring engine is deprecated,
and will be removed in a future version. To use the new Server Discover and Monitoring engine,
pass option { useUnifiedTopology: true } to the MongoClient constructor.
看来就是option中的一个设置要被删除了, 以后要使用MongoClient的构造器传给它. 现在开始看CRUD

新建记录

mongo在内部使用一个数据库连接池连接到数据库, 所以CRUD的代码都可以写在成功连接的回调函数中, 而且需要用到其中默认的client参数. 使用某个数据库, 并返回那个数据库链接的代码是:
mongoClient.connect(url, {useNewUrlParser: true}, function (error, client) {
    if (error) {
        console.log("AN error");
    }
    console.log(url, "Connected.");

    //指定某个数据库, 如果没有就会创建
    const db = client.db(databaseName);

});
之后向一个collection中插入一个document:
const db = client.db(databaseName);

//然后需要找一个表(collection), 向其中插入一条记录(Document)
db.collection('users').insertOne({
    name: "Cony",
    age: 6
});
使用可视化工具, 可以看到多了一个weathers数据库, 其中的Collections里有一个user, 点进去可以看到有1条Document, 其中有两个键值对就是刚刚写入的东西.还有一个内部自动生成的_id字段. 当然insertOne也可以传入回调:
db.collection('users').insertOne({
    name: "Jenny",
    age: 36
}, (error, result) =>{
    if (error) {
        return console.log("Unable to insert");
    }
    console.log(result.ops);
});
文档可以看MongoClient的文档. 除了insertOne之外, 还有insertMany方法, 使用方法类似, 只不过第一个参数换成对象数组, 就可以批量插入对象了.
db.collection("clusters").insertMany(
    [{good:'working',bepatient:'tocony'},{good:'working2',bepatient:'tocony2'},{good:'working22',bepatient:'tocony22'}],
    {},
    (error, result) =>{
        if(error)
            return console.log("Unable to insert");
        console.log(result.ops);
    }
)

ObjectID

在插入记录的时候, 都会有一个ObjectID, 这个东西其实就类似于SQL数据库中的主键. MongoDB中存储的文档必须有一个"_id"键。这个键的值可以是任何类型的,默认是个ObjectId对象:
{
    "_id" : ObjectId("5f35554763e5c44d507585dc"),
    "name" : "Cony",
    "age" : 6
}
在一个集合里面,每个文档都有唯一的"_id"值,来确保集合里面每个文档都能被唯一标识。由于是一个ObjectId对象, 所以也可以自己创建一个ObjectID对象来替代默认生成的对象. 根据官网文档, 自动生成的ObjectID是一个12字节长的值, 构成如下:
  1. 4字节的时间戳, 有了这个, 无需特意保存每条记录的生成时间
  2. 5字节的随机值
  3. 3字节的计数器, 初始化为一个随机的值
要创建一个ObjectID, 需要使用ObjectID对象:
const mongodb = require("mongodb");
const mongoClient = mongodb.MongoClient;
const ObjectID = mongodb.ObjectID;
在ES6下, 上边这三行还可以这么写(如果不使用mongodb变量的话):
const {MongoClient, ObjectID} = require("mongodb");
但是注意两处变量名称的不同, 需要与包中的类名一致才行. 然后就可以来创建自己的ObjectID了:
const id = ObjectID();

console.log(id);
打印出来类似如下5f3603b6cbdc9536f8915c0b, 还可以给构造器传入一个参数, 要求是12字节的字符串或者24个十六进制字符. 比如可以传入"123456789012", 打印出来的是313233343536373839303132. 传入"aabbccaabbccaabbccaabbcc", 被识别为十六进制字符串, 打印出来依然是aabbccaabbccaabbccaabbcc. 然后就可以来插入一个设置了自定义的ObjectID的对象:
const id = ObjectID("aabbccaabbccaabbccaabbcc");
MongoClient.connect(url, {useNewUrlParser: true}, function (error, client) {
    if (error) {
        console.log("AN error");
    }
    console.log(url, "Connected.");

    const db = client.db(databaseName);

    db.collection("customId").insertOne(
        {
            _id: id,
            game: "Baldur's Gate 3",
            release: 'unknown'
        }, {}, (error, result) => {
            if (error) {
                return console.log("Unable to insert");
            }
            console.log(result.ops);
        }
    );
 });
红字部分就覆盖了_id这个Field. 第一次执行可以插入, 第二次执行的时候, 由于ObjectId的值必须唯一, 但是我们没有更改过ObjectID, 因此就插入失败. 所以使用ObjectID, 就可以让策略更灵活, 如果我们规定用户名不能重复, 就可以想办法在ObjectID的生成方面做文章, 而不用每次插入都先去寻找. 对于系统生成的ObjectID,还有几个辅助方法可以方便的获取id或者十六进制字符串, 还有一个方便的功能就是其中自动记录了时间戳, 可以使用.getTimestamp()方法来快速获取时间戳:
const id = ObjectID();
console.log(id.id);
console.log(id.toHexString());
console.log(id.getTimestamp());
结果是:
<Buffer 5f 36 08 db 28 6f 69 3b 08 cc e0 d4>
5f3608db286f693b08cce0d4
2020-08-14T03:45:31.000Z
实际上Mongodb的这种ObjectID主要是为了分布式考虑的.

查找记录

能添加了接着就是来查找. 查找方法通过查看Collection文档可以知道, 有以FindFindOne为代表的方法.
db.collection("users").findOne({
        name: 'Cony',
        age:6
    }, (e,user)=>{
        if (e) {
            return console.log(e);
        }
        console.log(user);
    })
findOne的第一个参数是一个对象, 其中包含要查询的域和值, 如果像例子里一样是多个值, 则需要同时匹配才行. 如果这里将age改成1, 那么结果返回一个null, 注意, 找不到并不是发生错误, 也是一种正常的查询结果. 之后的回调函数是两个参数, 第一个是错误对象. 第二个是查询到的结果. findOne固定返回1个结果, 所以如果查找结果有多个, 会返回第一个. 也可以查询多个结果, 将findOne改成find方法, 如果打印user的话得到一个Cursor对象.
db.collection("users").find({
        age: 36
    }).toArray((error, users) => {
        console.log(users);
});

db.collection("users").find({
        age: 36
    }).count((error, count) => {
        console.log(count);
});
第一行查出了所有的users, 使用toArray然后传入回调. 第二行则是统计数量. 这样简单的查找也可以使用了.

Update

update方法也有一批, 分别是update, updateOne, updateMany update方法已经弃用, 现在使用updateOne和updateMany即可. 这两个方法传入的参数都一样, 分别如下:
  1. filter, 用于选择要更新哪些document
  2. update, 如何更新
  3. options, 设置选项
  4. callback, 永远的回调函数
看一个具体用法:
const updatePromise = db.collection("users").updateOne(
    {
        age: 36
    }, {
        $set: {
            age: 37
        },

    }
);

updatePromise.then((result) => {
    console.log(result)
}).catch(()=>{console.log("Error")});
这个不传入回调函数的话, 返回一个Promise, 所以也可以采用Promise的形式. filter相当于条件, 就和查找一样, 更新则必须使用规定好的对象, 其中使用特殊的update operators, 读文档可以发现, 特殊的操作符除了这个方法, 还会用于db.collection.findAndModify()方法. 所以这个操作符是update的核心, 遇到需求到这里来看一下有没有. 这个updateOne()如果查找到多个, 仅仅只会更新第一个,更新多个就使用updateMany().

Delete

一样先看collection的文档. 有一个drop方法用来删除collection. 删除document则可以使用deleteOnedeleteMany. 这两个都是三个参数: filter, options, callback. 所以用法很类似:
const deletePromise = db.collection("users").deleteOne(
    {
        age: 36
    }, {
    }
);

deletePromise.then((result) => {
    console.log(result)
}).catch(()=>{console.log("Error")});
增删改查全部都有了, 就可以来写点实际的东西了, React也可以逐步看起来了.
LICENSED UNDER CC BY-NC-SA 4.0
Comment