MongoDB 基础入门到后端接口开发进阶实战!
大家好,我是CoderBin
前言
本篇文章将给大家分享关于 MongoDB 的一些常用操作,最后再完成一个后端接口开发的项目。注意:这里不会带大家安装MongoDB数据库,并进行必要的配置,网上很多这方面的教程,这里不再赘述。
-
安装包可自行前往MongoDB官网进行下载安装,MongoDB下载地址
-
我看的安装教程是这个,亲测可用,点击前往
-
如果想深入学习MongoDB可前往 MongoDB官网、MongoDB中文网
-
已有 MongoDB 基础的,想查看项目,可直接拉到最后
1. 什么是MongoDB
MongoDB是一个文档数据库(以 JSON 为数据模型),由C++语言编写,旨在为WEB应用提供可扩展的 高性能数据存储解决方案。
文档来自于“JSON Document”,并非我们一般理解的 PDF,WORD 文档。
MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,数据格式是BSON,一种类似JSON的二进制形式的存储格式,简称Binary JSON ,和JSON一样支持内嵌的文档对象和数组对象,因此可以存储比较复杂的数据类型。
MongoDB最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。原则上 Oracle 和 MySQL 能做的事情,MongoDB 都能做(包括 ACID 事务)。
MongoDB概念与关系型数据库(RDBMS)非常类似:
SQL概念 | MongoDB概念 |
---|---|
数据库(databases) | 数据库(databases) |
表(table) | 集合(collection) |
行(row) | 文档(document) |
列(column) | 字段(field) |
索引(index) | 索引(index) |
主键(primary key) | _id(字段) |
视图(view) | 视图(view) |
表连接(table joins) | 局和操作($lookup) |
- 数据库(database):最外层的概念,可以理解为逻辑上的名称空间,一个数据库包含多个不同名称的集合。
- 集合(collection):相当于SQL中的表,一个集合可以存放多个不同的文档。
- 文档(document):一个文档相当于数据表中的一行,由多个不同的字段组成。
- 字段(field):文档中的一个属性,等同于列(column)。
- 索引(index):独立的检索式数据结构,与SQL概念一致。
- id:每个文档中都拥有一个唯一的id字段,相当于SQL中的主键(primary key)。
- 视图(view):可以看作一种虚拟的(非真实存在的)集合,与SQL中的视图类似。从MongoDB 3.4版本开始提供了视图功能,其通过聚合管道技术实现。
- 聚合操作($lookup):MongoDB用于实现“类似”表连接(tablejoin)的聚合操作符。
2. MongoDB 技术优势
MongoDB 基于灵活的 JSON 文档模型,非常适合敏捷式的快速开发。与此同时,其与生俱来的高可用、 高水平扩展能力使得它在处理海量、高并发的数据应用时颇具优势。
- JSON 结构和对象模型接近,开发代码量低
- JSON的动态模型意味着更容易响应新的业务需求
- 复制集提供99.999%高可用
- 分片架构支持海量数据和无缝扩容
1. 简单直观:从错综复杂的关系模型到一目了然的对象模型
2. 快速:最简单快速的开发方式
3. 灵活:快速响应业务变化
4. 原生的高可用、横向扩展能力。需要的时候无缝连接、应用全透明、多种数据分布策略、轻松支持TB-PB数量级
3. MongoDB 应用场景
从目前阿里云 MongoDB 云数据库上的用户看,MongoDB 的应用已经渗透到各个领域:
-
游戏场景,使用 MongoDB 存储游戏用户信息,用户的装备、积分等直接以内嵌文档的形式存储, 方便查询、更新;
-
物流场景,使用 MongoDB 存储订单信息,订单状态在运送过程中会不断更新,以MongoDB 内嵌 数组的形式来存储,一次查询就能将订单所有的变更读取出来;
-
社交场景,使用 MongoDB 存储存储用户信息,以及用户发表的朋友圈信息,通过地理位置索引实 现附近的人、地点等功能;
-
物联网场景,使用 MongoDB 存储所有接入的智能设备信息,以及设备汇报的日志信息,并对这些 信息进行多维度的分析;
-
视频直播,使用 MongoDB 存储用户信息、礼物信息等;
-
大数据应用,使用云数据库MongoDB作为大数据的云存储系统,随时进行数据提取分析,掌握行 业动态。
必要性声明:
我使用的 MongoDB 版本为
5.0.11
,数据库已处于登录状态,你可以使用以下命令进行登录:
如果配置了环境变量,且没有设置用户名密码,直接终端输入:
mongo
如果设置了用户名密码:
mongo -u用户名 -p密码
下面正式介绍数据库的一些操作
4. 数据库操作
4.1 查看数据库列表
语法:show databases
或:
show dbs
MongoDB 默认自带这三个数据库,这些据库名是保留的,可以直接访问这些有特殊作用的数据库。
- admin: 从权限的角度来看,这是"root"数据库。要是将一个用户添加到这个数据库,这个用户自动继承所有数据库的权限。一些特 定的服务器端命令也只能从这个数据库运行,比如列出所有的数据库或者关闭服务器。
- local: 这个数据永远不会被复制,可以用来存储限于本地单台服务器的任意集合
- config: 当Mongo用于分片设置时,config数据库在内部使用,用于保存分片的相关信息。
4.2 创建数据库
语法:use 数据库名称
注意:选择一个不存在的数据库,系统不会报错,等后期在这个数据库创建集合时,系统会自动创建该数据库。(这里主要用于演示,我自己创建了集合,后面会讲)
4.3 选择数据库
语法:use 数据库名称
4.4 查看当前所处数据库
语法:db
4.5 删除数据库
语法:db.dropDatabase()
先选择 test 数据库再进行删除。注意:删除数据库有风险,请谨慎操作!
5. 集合操作
5.1 创建集合
语法:db.createCollection(name, options)
注意:这里先隐式创建了 demo 数据库,然后连续创建了两个集合。这时候再查看数据库,就可以看到 demo 数据库了
创建集合时,一般直接指定集合名称即可,第二个options参数不常用,以下是可配置的选项:
字段 | 类型 | 描述 |
---|---|---|
capped | 布尔值 | (可选)如果为true,则创建固定集合。固定集合是指有着固定大小的集合, 当达到最大值时,它会自动覆盖最早的文档。 |
size | 数值 | (可选)为固定集合指定一个最大值(以字节计)。如果 capped 为 true,也 需要指定该字段。 |
max | 数值 | (可选)指定固定集合中包含文档的最大数量。 |
注意: 当向一个集合中插入一个文档的时候,如果集合不存在,则会自动创建集合。(集合的隐式创建)
5.2 查看集合列表
语法:show collections
或 show tables
5.3 删除集合
语法:db.集合名称.drop()
6. 文档基本CRUD
文档(document)的数据结构和 JSON 基本一样。 所有存储在集合中的数据都是 BSON 格式。
6.1 插入文档
6.1.1 单个插入
语法:db.集合名称.insert({})
6.1.2 多个插入
语法:db.集合名称.insertMany([{}, {}, ....])
注意:
- 我们并没有先创建course集合,是直接在 course上 插入了文档,这样系统会自动创建 course 集合
- 系统会自动给文档添加主键 _id 字段,我们也可以指定 _id 的值,进行覆盖
6.1.3 for循环批量插入
MongoDB 底层是用 JS 引擎实现的,所以支持部分 JS 语法,因此可以使用 for 循环实现数据的批量插入
for (let i=1; i<=10; i++) {
db.c1.insert({name: 'bin'+i, age: i})
}
将上述代码赋值到终端并回车,即可批量插入数据
6.2 查询文档
6.2.1 一般查询
语法:db.集合名称.find()
一般查询默认查询集合中的全部数据
6.2.2 条件查询
语法:db.集合名称.find({条件, [查询的列]})
- 条件
- 查询所有数据 —— {}或不写
- 查询id=2的数据 —— {id: 2}
- 既要id=2又要cuorse_name='linux' —— {id: 2, course_name: 'linux'}
- 查询的字段
- 查询全部字段 —— 不写
- 只显示course_name字段 —— {course_name: 1}
- 显示除了course_name以外的字段 —— {course_name: 0}
注意:不管如何查询,_id 字段会默认存在
需求:查询 course 集合中,teacher_id 为 2 的数据
多学一招:在查询命令后添加 pretty() 能够对数据进行显示格式化,更易于阅读
6.2.3 复杂条件查询
查询条件对照表
SQL | MongoDB |
---|---|
a = 1 | {a: 1} |
a > 1 | {a: {$gt: 1}} |
a >= 1 | {a: {$gte: 1}} |
a < 1 | {a: {$lt: 1}} |
a <= 1 | {a: {$lte: 1}} |
a <> 1 | {a: {$ne: 1}} |
查询逻辑对照表
SQL | MongoDB |
---|---|
a = 1 AND b = 1 | {a: 1, b: 1}或{$and: [{a: 1}, {b: 1}]} |
a = 1 OR b = 1 | {$or: [{a: 1}, {b: 1}]} |
a IS NULL | {a: {$exists: false}} |
a IN (1, 2, 3) | {a: {$in: [1, 2, 3]}} |
查询逻辑运算符
- $lt: 存在并小于
- $lte: 存在并小于等于
- $gt: 存在并大于
- $gte: 存在并大于等于
- $ne: 不存在或存在但不等于
- $in: 存在并在指定数组中
- $nin: 不存在或不在指定数组中
- $or: 匹配两个或多个条件中的一个
- $and: 匹配全部条件
需求,查找 course 集合中,teacher_id 大于 2 的数据
6.2.3 正则表达式匹配查询(模糊查询)
MongoDB 使用 $regex 操作符来设置匹配字符串的正则表达式。
需求:使用正则表达式查找 course 集合中,course_name 包含 "Java" 字符串的数据
简写形式:
db.course.find({course_name: /Java/}).pretty()
,用 / / 包含正则表达式(字符串)即可完成匹配操作
6.3 修改文档
修改文档要配合一些操作符,才能完美更新数据
操作符 | 格式 | 作用 |
---|---|---|
$set | {$set:{field:value}} | 指定一个键并更新值,若键不存在则创建 |
$unset | {$unset : {field : 1 }} | 删除一个键 |
$inc | {$inc : {field : value } } | 对数值类型进行增减 |
$push | { $push : {field : value } } | 将数值追加到数组中,若数组不存在则会进行初始化 |
$pushAll | {$pushAll : {field : value_array }} | 追加多个值到一个数组字段内 |
$pull | {$pull : {field : _value } } | 从数组中删除指定的元素 |
$addToSet | {$addToSet : {field : value } } | 添加元素到数组中,具有排重功能 |
$pop | {$pop : {field : 1 }} | 删除数组的第一个或最后一个元素 |
$rename | {$rename : {old_field_name : new_field_name } } | 修改字段名称 |
语法:db.集合名称.update({条件}, {修改器: {键:值}}, [options])
- options:描述更新的选项
- upsert: 可选,如果不存在update的记录,是否插入新的记录。默认false,不插入
- multi: 可选,是否按条件查询出的多条记录全部更新。 默认false,只更新找到的第一条记录
- writeConcern :可选,决定一个写操作落到多少个节点上才算成功。
下面将以完成需求的形式,来帮助大家掌握修改命令
需求1:将 course 集合中,teacher_id 为 2 的 course_name 改为 "java程序设计"
需求2:将 user 集合中,username 为 "CoderBin" 的 age 值加 1
需求3:将 user 集合中,username 为 "CoderBin" 的 age 值加 1,并且新增 phone 字段,值为 "123456789"
一次性写多个操作符的写法
6.4 删除文档
6.4.1 删除一条
语法:db.集合名称.deleteOne(条件)
需求:删除 user 集合中,username 为 "teacher_jack" 的数据
6.4.1 删除多条
语法:db.集合名称.deleteMany(条件)
需求:删除 user 集合中,age 为 "34" 的数据
注意:还有 remove() 也能实现删除操作,这里不再演示
7. MongoDB 排序&分页
7.1 排序
语法:db.集合名称.find().sort({键: 1|-1})
说明:键:根据这个键的值进行排序;1 升序,-1 降序
如下图,根据 age 进行排序
7.2 分页
语法:db.集合名称.skip(数字).limit(数字)
说明:
- skip 跳过指定的数量(可选),因为第一页不需要跳过
- limit 限制查询的数量(即显示多少条数据)
实际用法如下
多学一招:在查询命令后添加 count() 能够对集合中的文档数量进行统计
8. MongoDB 聚合查询
8.1 介绍
聚合操作处理数据记录并返回计算结果(诸如统计平均值,求和等)。
这里由于篇幅原因只演示聚合查询的一些简单操作,想深入学习的可前往官方文档学习
语法:
db.集合名称.aggregate([
{管道: {表达式}}
])
常用管道
管道 | 描述 |
---|---|
$group | 将集合中的文档进行分组,用于统计结果 |
$match | 过滤数据,只要输出符合条件的文档 |
$sort | 聚合数据进一步排序 |
$skip | 跳过指定文档数 |
$limit | 限制集合数据返回文档数 |
注意:以上管道并不完全,可前往官网进行学习其他管道 Aggregation Pipeline Stages — MongoDB Manual
常见表达式
表达式 | 描述 |
---|---|
$sum | 总和 $sum:1 同 count 表示统计 |
$avg | 平均值 |
$min | 最小值 |
$max | 最大值 |
8.2 案例
下面将通过几个案例,来帮助大家掌握这部分知识,这里提前创建了 c2 集合并有以下数据
案例一:统计男生女生的总年龄
// 答案
db.c2.aggregate([
{
$group: {
_id: "$sex",
rs: {$sum: "$age"}
}
}
])
_id: "$sex"
表示显示_id字段,按性别分组rs: {$sum: "$age"}
表示以 rs 字段展示年龄总和
结果如下:
案例二:统计男生女生的总人数
// 答案
db.c2.aggregate([
{
$group: {
_id: "$sex",
rs: {$sum: 1}
}
}
])
结果如下:
案例三:统计学生总数和平均年龄
// 答案
db.c2.aggregate([
{
$group: {
_id: "null",
total_num: {$sum: 1},
total_avg: {$avg: "$age"}
}
}
])
结果如下:
案例三:查询男生、女生人数,按人数升序
// 答案
db.c2.aggregate([
{$group: {_id: "$sex", rs: {$sum: 1}}},
{$sort: {rs: 1}}
])
结果如下:
好的,到此为止,关于 MongoDB 的基本操作你已经会了,但是MongoDB还有一些进阶操作,例如索引、备份还原、权限机制等等,这些对于后面的接口开发关系不大(
前端切图仔数据库学那么深干嘛☺),这里不再详细介绍。接下来让我们转向后端实战环节吧 ^_^
注意:
- 这里后端采用的是 Node.js + Express 的形式,开启后端服务,来实现接口开发
- 使用 mongoose 第三方库去简化对数据库的操作
- 这次实现的后端接口内容是,用户登录注册,基本增删改查(其他的大同小异)
- 使用 Postman 工具来测试接口
9. mongoose 的简单使用
在node中,mongooes是一个第三方模块,是一个对象文档模型(ODM)库,它对Node原生的MongoDB模块进一步的优化封装,可以通过操作对象模型来操作MongoDB数据库。
安装
yarn add mongooes
在 db.js 中,连接数据库
const mongoose = require('mongoose')
// 1. 连接数据库
const db = mongoose.createConnection(
// 'mongodb://localhost:27017/集合' -- 没有设置用户名密码用这个
'mongodb://用户名:密码@localhost:27017/集合?authSource=admin',
{ useNewUrlParser: true, useUnifiedTopology: true },
err => {
if (err) {
return console.log('数据库连接失败:', + err)
}
console.log('数据库连接成功!')
}
)
module.exports = db
在 index.js 中,演示增删改查操作
const db = require('./db')
// 2. 设置数据模型(声明是哪个集合,限制字段个数和字段类型)
const User = db.model('users', {
username: { type: String, required: true },
age: { type: Number, default: 18 }, // 默认值为18
password: Number, // 这里是简化写法,等同于 {type: Number} 这种写法
phone: Number
})
//========================================================
// 3. 创建实例操作
// 增
const insertUserObj = new User({
username: 'teacher_tom',
password: 123456,
age: 44,
phone: 12345678900
})
insertUserObj.save().then(res => {
console.log('插入成功: ' + res)
}).catch(err => {
console.log('err: ' + err)
})
//========================================================
// 查询
// 查询全部
User.find().then(res => {
console.log('查询成功:' + res)
return res
}).catch(err => {
console.log(err)
})
// 查询一条
// User.findOne({ username: 'CoderBin' }).then(res => {
// console.log('查询成功:' + res)
// }).catch(err => {
// console.log(err)
// })
// 分页查询
// User.find({}).skip(2).limit(2).then(res => {
// console.log('查询成功:' + res)
// return res
// }).catch(err => {
// console.log(err)
// })
//========================================================
// 修改
User.findOneAndUpdate(
{ username: 'CoderBin'},
{ $set: { password: 123666 } },
).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
//========================================================
// 删除
User.findOneAndDelete({ username: '小明'}).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
关于 mongoose 的简单使用就要这里,如果想学习 mongoose 的更多操作,可以前往 Mongoose官方文档、Mongoose中文文档
10. 项目初始化
本项目会带大家从零搭建后端接口服务,尽量做到模块层次划分清晰。项目中用到的 express 部分不难,如果有express使用经验会更好,不会也没关系,如果想仔细学习的可以前往Express中文网
10.1 安装依赖
首先新建个项目文件夹 express-mongodb-mongoose
,用 vscode 打开,依次在终端输入如下命令,用于安装 express 库 和 mongoose 库
npm init -y
yarn add express mongoose
然后就能得到 package.json
文件,以下是我的配置内容
10.2 层次划分
我们需要将项目进行层次划分,每个层都有独立的作用,最终会连接到一起,相互调用整合,来实现项目的整体搭建。
下面标注的文件夹和文件都需要自己创建,第一眼或许感觉很乱,但请不要急,慢慢看。
- 数据库连接层 —— 连接数据库,向外导出 db 对象
- 数据控制层 —— 将数据进行逻辑处理,发送响应信息
- 模型层 —— 数据库每个集合对应的属性模型
- 路由层 —— 设置路由,即我们要请求的API地址
其实还可以从
数据控制层
抽离出数据库访问层
,但是这里怕大家混乱,就不做设置,统一在数据控制层
进行操作
11. 测试服务与接口
首先在 app.js 文件中编写如下代码,并在终端执行 node app.js
命令
// 导入 express 模块
const express = require('express')
// 创建 app 对象
const app = express()
// 配置解析表单数据的中间件
app.use(express.urlencoded({ extended: false }))
// 请求 / 地址,响应数据
app.get('/', (req, res) => {
// 使用 send() 方法响应数据
res.send({
status: 200,
msg: 'ok'
})
})
// 监听3000端口,开启服务
app.listen(3000, () => {
console.log('serve running at http://localhost:3000');
})
能看到输出,就表示开启了服务,但不代表接口能用,还需要使用 Postman 工具,去测试接口
在 Postman 中进行如下步骤,得到响应数据即代表接口可用
12. 连接数据库
在 connection/db.js 文件中连接数据库
const mongoose = require('mongoose')
// 1. 连接数据库
const db = mongoose.createConnection(
// 'mongodb://localhost:27017/demo'
'mongodb://用户名:密码@localhost:27017/demo?authSource=admin',
{ useNewUrlParser: true, useUnifiedTopology: true },
err => {
if (err) {
return console.log('数据库连接失败:', + err)
}
console.log('数据库连接成功!')
}
)
// 向外导出,在模型层需要使用
module.exports = db
执行 node ./connection/db.js
命令,有以下输出即代表数据库连接成功
12. 创建 User 模型
在 models/User.js 文件中编写如下代码,创建模型
const db = require('../connection/db')
// 指定集合名称,并对字段进行约束
const User = db.model('users', {
username: {
type: String,
required: true
},
age: {
type: Number,
required: true
},
password: {
type: String,
required: true
},
phone: {
type: Number,
required: true
},
})
// 向外导出 User 模型,在数据控制层中会使用
module.exports = User
13. 用户注册接口实现
在前面的准备工作都好后,就可以去完成具体的功能接口了。先实现用户注册的接口,简单点说就是获取到前端传递的数据,经过数据控制层,存储在数据库当中,后端再返回一个成功的响应。
- 在 controllers/userController.js 中,定义一个
UserController
类,在里面定义各种关于 user 的操作方法,最后将之实例化并导出,在路由层
使用
// 导入User模型
const User = require('../models/User')
class UserController {
// 注册用户的方法
async register(req, res) {
// 创建模型对象
const userObj = new User(req.body)
// 保存数据到数据库
const userInfo = await userObj.save()
// 调用 res.send() 方法,向客户端响应结果
res.send({
status: 201,
msg: '注册成功!',
data: userInfo
})
}
}
// 向外导出实例,在路由层使用
module.exports = new UserController()
- 在 routers/usre.js 中设置路由
const express = require('express')
// 导入用户数据控制器
const UserController = require('../controllers/UserController')
// 创建路由对象
const router = express.Router()
// 设置路由
// 该接口需要提供表单数据,所以使用post请求
// 参数说明:router.post(api请求地址, 执行控制器里面的注册方法)
router.post('/register', UserController.register)
// 导入路由对象
module.exports = router
- 在 app.js 中注册路由,看代码注释部分即可
const express = require('express')
const userRouter = require('./routers/user')
const app = express()
app.use(express.urlencoded({ extended: false }))
// 注册路由,并定义接口前缀为user
app.use('/user', userRouter)
app.listen(3000, () => {
console.log('serve running at http://localhost:3000');
})
最后在终端开启服务 node app.js
,在 Postman 进行测试
查看数据库,检查是否插入用户数据成功。注:这里使用 Navicat 可视化工具进行查看
到这里,你就实现了注册接口的开发!
注意:在一个上线的项目当中,注册接口的逻辑当然不会跟我上面的逻辑那样简单,你至少得进行密码加密存储,判断是否缺失参数、用户名是否重复等等的边界判断,由这些情况导致的插入数据失败,还得返回失败的响应,这些就留给你自己去思考实现啦!
14. 用户分页查询接口实现
接来下我们实现用户分页查询的接口,在 controllers/userController.js 中,添加findUsers
方法
// 分页查询用户数据
async findUsers(req, res) {
// 拿到查询的数据(不传page_num和page_size则默认查询全部)
const query = req.query
// 计算 skip 值
let skip = (parseInt(query.page_num) - 1) * parseInt(query.page_size)
// 使用分页查询方法
const users = await User.find().skip(skip).limit(query.page_size)
res.send({
status: 201,
msg: '查询成功!',
data: users
})
}
在 routers/usre.js 中设置路由,注意:不需要再去 app.js 注册路由了,因为前面已经注册过user的路由对象了,我们只是在这个路由对象再添加一个路由而已
const express = require('express')
// 导入用户逻辑
const UserController = require('../controllers/UserController')
// 创建路由对象
const router = express.Router()
// 设置路由
router.post('/register', UserController.register) // 注册接口
router.get('/users', UserController.findUsers) // 查询全部用户接口
// 导入路由对象
module.exports = router
因为分页查询需要多个数据存在,所以我新增了一些数据,现在总共5条用户数据,如下图
开启服务,使用 Postman 进行测试
注意:由于后端指定了 page_num 为第几页,page_size 为几条数据。所以这两个字段不能写错
15. 项目地址
本项目其实还有其他接口实现,比如用户修改,删除,指定id查询等,如下图:
以上接口经过测试,皆为可用
这些接口逻辑操作大同小异,这里我只演示了两种接口的实现,如果想要源码学习的可以前往 gitee源码地址 下载查看
本项目的不足之处还有很多,只是在 Node.js 平台中,操作 MongoDB 数据库,实现简单的后端接口开发。但是你可以在此基础上不断完善,考虑后端接口设计的方方面面,这样才可以运用到前后端的项目当中。希望本文能够对正在观看的你有所帮助,谢谢。
往期推荐 💐 🌸 🌹 🌻 🌺 🍁
高阅读好文:
【3】Vue内置指令大全
每文一句:无限相信书籍的力量,是我的教育信仰的真谛之一
本次的分享就到这里,如果本章内容对你有所帮助的话可以点赞+收藏。文章有不对的地方欢迎指出,有任何疑问都可以在评论区留言。希望大家都能够有所收获,大家一起探讨、进步!
转载自:https://juejin.cn/post/7136365535796658183