本来在前言里面写了一堆数据库类型介绍、为什么要学习Mongodb
数据库等等,后来我又全删了,这种概念的东西看了也只能混个脸熟,即使明白了也过一阵子忘记了,只有真正用起来了才逐渐明白这些概念,如果刚一开始接触太多概念容易让人迷惑(因为我就这样…),实践出真知嘛。
说一点,这篇文章重点是教你如何快速入门MongoDB,至于如何去安装它们不会花篇幅描述。
这里有一份官方的安装MongoDB的指南Install MongoDB,不过是英文的,你可以使用谷歌翻译。
另外一份官方文档,使用MongoDB Shell
连接数据库,mongo shell。
下面是我所安装的版本:
此外,我接下来所讲解的操作都是在MongoDB Shell
环境下进行的,如果需要在其他环境下进行需要安装或引入相关的驱动。当然为了方便,我在可视化工具下进行操作,因为可视化工具集成了MongoDB Shell
环境。
虽然我提倡不接触那些晦涩难搞的概念,比如事务
,但接下来的概念你还是需要知道的!
学过SQL
的同学应该知道,进行数据库操作有以下操作(略过登陆权限啥的):
上面每一步都需要输入相关的SQL
脚本去执行。但是在MongoDB
中不需要主动去创建数据,也不需要去建“表”,你只要直接在“表”中插一条数据,那么MongoDB
会帮你自动创建数据库和“表”,这是动态进行的,很灵活。这个“表”就是MongoDB
中的集合
(collection),表中每一条数据都被称为文档
。从这我们可以看出来,关系型数据库和非关系型数据库之间的一个区别了吧(当然我还是不会说太多)。
一张图来说明MongoDB
中数据库
、集合
、文档
之间的关系:
BSON是一种二进制序列化格式,用于在MongoDB中存储文档和进行远程过程调用。 BSON规范位于bsonspec.org
它的数据结构是一种类似JSON的数据结构,可以存储各种类型数据,比如二进制数据图像、视频等。特点如下:
_id
字段设置为主键,保证不重复搞前端的同学肯定明白了,这和JS的灵活性很像啊。连字段都可以动态加入,这也太牛逼了。
在平时业务中,我们大部分的操作都是CRUD
操作,在此之前,先介绍几个基本操作,打开你的MongoDB Shell
或者 Studio 3T
点击Intelli Shell
都是一样的(个人推荐后者)。
show dbs
db
show collections
db..drop()
db.dropdatabase()
use
上面多次提到了这个CRUD
,意思我们都知道了,英文单词就是下面四个:
可以说这一小节无疑是最重要的一部分了,上面也提到过了,我们的业务大部分就是增删改查操作了。下面讲解这些命令的时候会有一部分是与SQL
的对比,这一部分来自官网(看了半天的英文…)。
以下示例均在数据库my_test
,集合students
中进行。
db.collection.insert(
<文档或文档数组>,
{
writeConcern:<文档>,
ordered:
}
)
该方法接受两个参数,第一个参数就是要插入的文档,可以是一个对象表示插入一个文档,也可以是一个对象数组表示插入多个文档。第二个参数一般不需要写,是一些插入配置选项。插入后,会返回一个对象来表明插入的状态。返回的对象属性如下:
{
acknowledged: ,
_id: Array
}
下面要介绍的方法和上面的是一样的,只不过下面更加有语义化,推荐使用下方的方法。
db.collection.insertOne(
<文档>,
{
writeConcern:<文档>
}
)
db.collection.insertMany(
<文档或文档数组>,
{
writeConcern:<文档>,
ordered:
}
)
比如我现在向students
集合中插入几个文档,
db.students.insertMany(
[
{
name: '老曹',
age: 22,
gender: '男',
address: '湘潭',
like: {
singers: ['中岛美雪', '黄家驹', '张国荣'],
books: ['你不知道的JavaScript', '吉他自学三月通', '小孩']
}
},
{
name: '老罗',
age: 24,
gender: '男',
address: '南宁',
like: {
singers: ['泰勒斯威夫特', '杨千嬅'],
books: ['高等数学', '初级日语', '挪威的森林']
}
},
{
name: '老陈',
age: 23,
gender: '女',
address: '株洲',
like: {
singers: ['陈奕迅', '林宥嘉'],
books: ['从你的世界路过', '顾城的诗']
}
},
{
name: '老吴',
age: 21,
gender: '女',
address: '铜仁',
like: {
singers: ['张学友', '刘德华', '老曹'],
books: ['PHP从入门到精通']
}
},
]
)
使用db.students.find()
可以查看刚刚插入的文档:
db.collection.find(
query: ,
projection:
)
query
:查询的条件,当不传或者是一个空对象的时候表示查询所有文档。
projection
:反射条件,即决定哪些字段显示。字段被设置为1
或true
的时候才显示,反之0
或者false
不显示。
下面对上面的students
集合进行不同条件的查询来模拟大部分应用场景:
name
等于老曹
的文档db.students.find({ name: '老曹' })
在SQL
中表示为:
SELECT * FROM students
WHERE name = '老曹'
age
大于21,like
中singers
值在黄家驹
、陈奕迅
之中的文档db.students.find(
{
age: { $gt: 21 },
'like.singers': { $in: ['黄家驹', '陈奕迅'] }
}
)
上面引入的$gt
、$in
是一种操作符,表示大于
和在
,类似的还有常用的有:$lt
、$or
等等。
like.singers
是为了查询嵌套文档而使用的.
操作符。
在SQL中表示:
SELECT * FROM students
WHERE age > 21
AND like.singers IN ('黄家驹', '陈奕迅')
PHP入门到精通
或者gender
为男
的文档db.students.find(
{
$or: [ { gender: '男' }, { 'like.books': 'PHP从入门到精通' }]
}
)
在SQL中表示:
SELECT * FROM students
WHERE gender = '男'
OR like.books = 'PHP从入门到精通'
更多用法请查看文档。
db.collection.findOne(
query: ,
projection:
)
db.collection.update(
<query>,
<update>,
{
upsert: <boolean>,
multi: <boolean>,
writeConcern: <document>,
collation: <document>,
arrayFilters: [ <filterdocument1>, ... ],
hint: <document|string> // Available starting in MongoDB 4.2
}
)
该方法返回一个WriteResult
文档,其中包含操作的状态。
常见参数说明如下:
query
:查询条件。
update
:更新符合query条件的文档。
upsert
:默认值为 false。如果设置为true,则在没有文档符合查询条件时创建一个新文档。
multi
:默认值为 false。如果设置为true,则更新多个符合query条件的文档。
其他参数不是很常用,所以不说明,有兴趣可以点这里查看更多的参数用法。下面举几个例子来说明该方法的用法。
name
为老吴
的人,把他的age
增加两岁,并且把gender
设置为男
db.students.update(
{ name: '老吴' },
{
$set: { gender: '男' },
$inc: { age: 2 }
}
)
在SQL中等同于:
UPDATE students
SET gender = '男'
age = age + 2
WHERE name = '老吴'
age
不小于22岁的文档,将address
字段删除。db.students.update(
{ age: { $gte: 22 } },
{
$unset: { address: 1 }
},
{
multi: true
}
)
类似于SQL中的:
ALTER TABLE students
DROP COLUMN address
(ALTER TABLE
语句用于在已有的表中添加、删除或修改列。不等同于MongoDB中的删除字段,但是类似)
张学友
的文档增加一本喜欢的书离散数学
db.students.update(
{ 'like.singers': '张学友' },
{
$push: { 'like.books': '离散数学' }
}
)
说一下上面出现的操作符,以及一些其他常用操作符:
$set
:用来更新字段的值
$inc
:可以递增数值
$unset
:删除某个字段
$push
: 向数组添加元素
$pop
:像数组移除元素
db.collection.updateOne(
<query>,
<update>,
{
upsert: <boolean>,
multi: <boolean>,
writeConcern: <document>,
collation: <document>,
arrayFilters: [ <filterdocument1>, ... ],
hint: <document|string> // Available starting in MongoDB 4.2
}
)
db.collection.updateMany(
<query>,
<update>,
{
upsert: <boolean>,
multi: <boolean>,
writeConcern: <document>,
collation: <document>,
arrayFilters: [ <filterdocument1>, ... ],
hint: <document|string> // Available starting in MongoDB 4.2
}
)
后两个方法语义更强推荐使用,其实还有一些方法也可以更新不够我认为语义更强的还是最后这两种方法,推荐大家使用。
db.collection.deleteOne(
<filter>,
{
writeConcern: <document>,
collation: <document>
}
)
db.collection.deleteMany(
<filter>,
{
writeConcern: <document>,
collation: <document>
}
)
用法和其他方法是差不多的,我相信大家也肯定知道怎么使用,只不过个人不推荐使用删除操作,对数据库删除是一个很危险的操作,搞不好你就凉凉了。
看到这里的话,我相信大家肯定对基本的操作已经掌握了,相对于SQL
语句来说,MongoDB Shell
更加灵活和简单,上手速度很快。
在MongoDB中支持原生Node.js的驱动,你可以在你的项目中使用以下命令来下载该驱动:
npm i mongodb -S
具体api请参考:MongoDB Node.js Driver
不管什么语言的驱动,底层都是支持MongoDB Shell
的,所以虽然有了这个驱动后可以对数据库操作了,但毕竟是原生Node,你懂得还是有点缺陷的,所以就出现了基于mongodb
封装的模块mongoose
。用起来十分方便,不多说,下面告诉大家如何使用。
Mongoose是一个对象文档模型(ODM)库,它对Node原生的MongoDB模块进行了进一步的优化封装,并提供了更多的功能。
Moogoose
有三大核心概念:Schema
、Model
、Document
,下面我们详细来说。
上官网弄个概念先:
Each schema maps to a MongoDB collection and defines the shape of the documents within that collection.
翻译过来就是:每个模式都映射到MongoDB集合并且定义该文档的形状。
说白了,模式就是一种约束。在没有约束的情况下,得益于MongoDB的动态和灵活性,只要给任意一个文档临时添加一个字段后,在其他文档中也会出现该字段,值是空的。如果在一开始就定义好结构,严格控制好每个集合的类型,那以后维护起来也不会很困难。
个人认为和TypeScript
中类型定义有点像,还有其他语言中的class
类,都严格约束着数据结构。用法如下:
mongoose
模块中的Schema
属性,该属性是一个构造函数。option
对象,定义与数据库集合的交互。 new Schema(definition,option)
option
常见选项如下:
autoIndex
true
bufferCommands
true
capped
collection
Schema
的集合名称id
true
_id
true
strict
Schema
的对象不会被插入进数据库,默认true
我们给上面的students
集合添加一个模式:
const { Schema } = require('mongoose');
const studentSchema = new Schema({
name: { type: String },
age: { type: Number },
gender: {
type: String,
default: 'male'
},
like: {
singers: Array,
books: Array
}
});
Model对象作为集合中的所有文档的表示,相当于MongoDB数据库中的集合collection。
创建模型对象需要使用mongoose
的model
方法,语法如下:
model(name, [schema], [collection] , [skipInit])
• name
参数相当于模型的名字,以后可以同过name
找到模型。
• schema
是创建好的模式对象。
• collection
是要连接的集合名。
• skipInit
是否跳过初始化,默认是false
。
一旦把一个Schema
对象编译成一个Model
对象,你就完全准备好开始在模型中访问、添加、删除、更新和删除文档了。也就是说有了模型以后我们就可以操作数据库了。
给上面的例子创建一个Model
,名字为students
,加上之前的整个代码如下:
// student
const { Schema, model } = require('mongoose');
const studentSchema = new Schema({
name: { type: String },
age: { type: Number },
gender: {
type: String,
default: 'male'
},
like: {
singers: Array,
books: Array
}
});
module.exports = model('students', studentSchema);
Document表示集合中的具体文档,相当于集合中的一个具体的文档
有了Model
对象,就相当于已经和数据库的集合建立关系了,可以进行CRUD
操作了。我们要做的就是在数据库操作中引入model
就可以了。导出的Model
其实就是一个构造函数,不用我说你应该也知道有两种方式来操作数据库了吧。
Document
对象,可以调用Document上的方法,相关API Document API简单就介绍到这了,API的学习大家看文档就好了(我实在顶不住了…)
在此之前,先初始化一个项目test
mkdir test
cd test
npm init -y
npm i mongoose -S
在后端应用中我只模拟了开发环境和生产环境,如果有其他需求的可以自行更改。
package.json
设置环境变量NODE_ENV
"scripts": {
"dev": "set NODE_ENV=development&&node controller/student/search.js",
"build": "set NODE_ENV=production && node app.js"
},
注意:后面的node
执行文件是为了启动方便而随便找的文件,你们可以根据项目自行更改。
我暂时没有使用webpack
,因为我只是讲数据库,后面如果我完善了我的应用我会给大家单独写一篇文章。
环境变量设置好后,在根目录下创建一个config
文件夹,在index.js
中根据环境来引入不同的配置:
index.js
/*
windows:生产环境 + 开发环境,其他系统默认生产环境
*/
const os = process.env.os;
const mode = os === 'Windows_NT' ? process.env.NODE_ENV : 'production';
module.exports = {
mode,
...(mode === 'development' ? require('./dev.config') : require('./prod.config'))
};
下面是我开发环境的配置,生产环境我暂时没写,如有需要请自行配置。
dev.config.js
// 开发环境配置
const dbOptions = {
useNewUrlParser: true,
useUnifiedTopology: true
}
module.exports = {
// mongodb
DB_NAME: 'my_test',
DB_HOST: '127.0.0.1',
DB_PORT: '27017',
DB_OPTIONS: dbOptions,
// other configs...
}
配置好后,我们可以来连接数据库啦!
在根目录下面创建一个libs
存放数据库的连接操作,在目录下面创建database.js
。
引入前面的配置,关于options的说明请点击:Class: MongoClient
database.js
// 创建数据库连接
const mongoose = require('mongoose');
const db = mongoose.connection;
const { DB_NAME, DB_HOST, DB_PORT, DB_OPTIONS} = require('../config');
const uri = `mongodb://${DB_HOST}:${DB_PORT}/${DB_NAME}`;
// 连接数据库
connectMongoDb();
// 监听连接事件
db.once('open', handleConnectOpen);
db.on('error', handleConnectAfterError);
async function connectMongoDb() {
try {
await mongoose.connect(uri, DB_OPTIONS);
} catch (error) {
handleConnectInitError(error);
}
}
// 初始化连接错误处理
function handleConnectInitError(error) {
console.log(error);
}
// 连接后错误处理
function handleConnectAfterError(error) {
console.log(error);
}
// 成功连接后的处理函数
function handleConnectOpen(error) {
if(!error) {
console.log('数据库连接成功~~~')
}
}
Schema
和Model
在根目录创建一个models
目录,存放不同model
。我们创建一个student.js
来举例:
student.js
// student
const { Schema, model } = require('mongoose');
const studentSchema = new Schema({
name: { type: String },
age: { type: Number },
gender: {
type: String,
default: 'male'
},
like: {
singers: Array,
books: Array
}
});
module.exports = model('students', studentSchema);
建立一个controller
目录,该目录下是每不同模块的逻辑操作。在该目录下面再建一个student
文件夹,存放对student
各种CRUD
操作,下面以查询所有文档为例:
search.js
require('../../libs/database');
const StudentModel = require('../../models/student');
(async function () {
let data = await StudentModel.find({});
console.log(data)
})();
使用npm run dev
执行 search.js
(在package.json
里面配置过),就可以成功取到数据库里面的数据了。
说明:数据是提前插进去了,在下一次插入数据的时候就能检测数据格式啦。
有几个是空的,以后大家做项目应该可以用上,我简单说明如下:
utils
:存放工具模块static
:静态资源模块routers
:路由模块log
:服务器日志app.js
:主入口还有其他的,以后再来补充上吧.
这篇文章总体来说还是有很多缺陷,但是写这篇文章让我本身也学到不少知识,今后还有很长的一段路要走,如果你有缘看到这篇长文,希望对你有帮助,加油~
[1] The MongoDB 4.2 Manual
[2] Mongoose v5.7.12