上一节结束的时候就说还差数据库了, MongoDB和Node.js发行于同一年, 所以Node应用经常使用MongoDB.
NoSQL数据库与SQL数据库的对应关系
MongoDB用过, 但是没有好好看过. 这次就来结合Node一起用用, 这样Node后端基本就完整了.
SQL与NoSQL的对应关系如下:
- Table – Collection
- Row/Record – Document
- Column – Field
MongoDB的官网在https://www.mongodb.com/, 点击其中的Software – MongoDB Community Server,
这个是免费的版本, 然后下载安装, 先来安装CentOs版本, 选择旁边的4.4.0, CentOS 7+, 然后是tgz版本.
依照官网的安装指南来进行安装:
- 在
/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
- 然后使用yum直接安装
sudo yum install -y mongodb-org
安装并启动成功之后, MongoDB默认绑定127.0.0.1, 即只能在本机访问, 在配置文件/etc/mongod.conf
中修改如下:bindIp: 0.0.0.0
, 这样就是绑定了所有IPv4和IPv6地址, 之后使用systemctl
即可. 和MySQL安装有相似之处.
restart mongod
启动、停止和检测状态在CentOS 7中如下:
systemctl start mongod
systemctl stop mongod
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字节长的值, 构成如下:
- 4字节的时间戳, 有了这个, 无需特意保存每条记录的生成时间
- 5字节的随机值
- 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文档可以知道,
有以Find和FindOne为代表的方法.
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即可.
这两个方法传入的参数都一样, 分别如下:
filter
, 用于选择要更新哪些documentupdate
, 如何更新options
, 设置选项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则可以使用deleteOne
和deleteMany
.
这两个都是三个参数: filter, options, callback. 所以用法很类似:
const deletePromise = db.collection("users").deleteOne( { age: 36 }, { } ); deletePromise.then((result) => { console.log(result) }).catch(()=>{console.log("Error")});
增删改查全部都有了, 就可以来写点实际的东西了, React也可以逐步看起来了.