上一节结束的时候就说还差数据库了, MongoDB和Node.js发行于同一年, 所以Node应用经常使用MongoDB.
- NoSQL数据库与SQL数据库的对应关系
- 在Node中使用MongoDB
- 新建记录
- ObjectID
- 查找记录
- Update
- Delete
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
restart mongod
即可. 和MySQL安装有相似之处.
启动、停止和检测状态在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
, 用于选择要更新哪些document
update
, 如何更新
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也可以逐步看起来了.