MongoDB数据库
MongoDB
以下为学习过程中的极简提炼笔记,以供重温巩固学习
学习准备
准备工作
学习目的
MongoDB 简介
Mongodb 是什么
MongoDB 是一个基于分布式文件存储的数据库,官方地址 https://www.mongodb.com/
数据库是什么
- 数据库(DataBase)是按照数据结构来组织、存储和管理数据的
应用程序
- 本质是应用程序、软件
数据库的作用
数据库的主要作用就是 管理数据
,对数据进行 增(c)、删(d)、改(u)、查(r)
数据库管理数据的特点
相比于纯文件管理数据(如纯json文件),数据库管理数据有如下特点:
- 速度更快
- 数据库软件内部有特定的数据结构,提高访问速度
- 数据库软件会借助内存对数据作缓存
- 扩展性更强
- 可创建多个数据库节点,减少单个节点压力
- 安全性更强
大部分数据库都会对数据作加密存储,拥有身份校验、权限管理等功能
另注:
- 数据库有自己独立的通信协议,不能直接用http服务去请求
- vscode可以安装mongodb for vscode插件
为什么选择 Mongodb
操作语法与 JavaScript 类似(对象.对象.),容易上手,学习成本低
MongoDB 核心概念
Mongodb 中有三个重要概念需要掌握
数据库(database) 数据库是一个数据仓库,数据库服务下可以创建很多数据库,数据库中可以存放很多集合
集合(collection) 集合类似于 JS 中的数组,在集合中可以存放很多文档
文档(document) 文档是数据库中的最小单位,类似于 JS 中的对象
可简单理解:
- 库=多个集合/多个表
- 集合=属性对应的数组信息(多个)就是集合
- (同一种属性/类型的数据,集合拥有该属性下的,多份同属性/类型,但内容不同的数据)
- 不同的集合/数组,记录了不同属性/类型的数据
- 同一个集合,记录了不同份的数据
- 同一个集合,可以理解为一份数据表,这张表记录了多份同属性/类型的数据
- 文档=集合中有多个对象,每一个对象就是一个文档
- (该属性下的其中一份/组数据)
- 一个文档,就是 对应属性下的一份数据
- 对应属性下的一份数据,拥有多个记录信息的字段/属性键,字段=数组集合.对象.属性键
大家可以通过 JSON 文件来理解 Mongodb 中的概念
- 一个
JSON
文件 好比是一个数据库
,一个 Mongodb 服务下可以有 N 个数据库 - JSON 文件中的
一级属性的数组值
好比是集合
- (集合数组元素,通过数组集合了属性/类型一致的多条数值不同的数据)
- (拥有该属性下的多份同属性/类型,但内容不同的数据)
- 数组中的对象好比是
文档
- (该属性下的其中一份数据)
- 对象中的属性有时也称之为
字段
一般情况下
- 一个项目使用一个数据库
- 一个集合会存储同一种类型的数据
- 一个集合存储同一种标识/用途/特征的数据信息
JSON 文件示例,来理解 Mongodb 中的概念:
// 一个数据库服务下(数据库文件夹下),可以创建多个数据库(多个*.json文件)
// 一个 *.json文件,就是一个数据库
// 一个项目使用一个数据库
{
// 属性对应的数组信息(多个)就是集合
// 存储同一种标识/用途/特征的数据信息
"accounts": [
// 一个文档
// 即:对应属性下的一份数据
{
// 多个记录信息的字段/属性键
// 字段=数组集合.对象.属性键
"id": "3-YLju5f3",
"title": "买电脑",
"time": "2023-02-08",
"type": "-1",
"account": "5500",
"remarks": "为了上网课"
},
// 另一个文档
{
"id": "3-YLju5f4",
"title": "请女朋友吃饭",
"time": "2023-02-08",
"type": "-1",
"account": "214",
"remarks": "情人节聚餐"
},
{
"id": "mRQiD4s3K",
"title": "发工资",
"time": "2023-02-19",
"type": "1",
"account": "4396",
"remarks": "终于发工资啦!~~"
}
],
// 不同的集合/数组,记录了不同属性/类型的数据
"users": [
{
"id": 1,
"name": "zhangsan",
"age": 18
},
{
"id": 2,
"name": "lisi",
"age": 20
},
{
"id": 3,
"name": "wangwu",
"age": 22
}
]
}
旧版本下载安装与启动
下载地址: https://www.mongodb.com/try/download/community
- 建议选择
zip
类型, 通用性更强 - 大版本5及之前的,包含了客户端程序,6及以后的,客户端程序与服务端程序分离
配置步骤如下:
- 将压缩包移动到
C:\Program Files
下,然后解压
- C:\Program Files是C盘中专门用于存放程序软件的目录
- 创建
C:\data\db
目录,mongodb 会将数据默认保存在这个文件夹 - 以 mongodb 中 bin 目录作为工作目录,启动命令行
- 运行命令
mongod
- 本质上是运行服务端程序 mongod.exe
看到最后的 waiting for connections
则表明服务 已经启动成功
然后可以使用 mongo
命令连接本机的 mongodb 服务
注意:
- 为了方便后续方便使用 mongod 命令,可以将 bin 目录配置到环境变量 Path 中
千万不要选中服务端窗口的内容
,选中会停止服务,可以敲回车
取消选中
- 客户端窗口不限制,可以选中
新版本下载安装与启动
下载地址: https://www.mongodb.com/try/download/community
- 大版本5及之前的,包含了客户端程序,6及以后的,客户端程序与服务端程序分离
- 建议选择
zip
压缩包类型,通用性更强(其实msi自解压安装程序也是可以的,可以在过程中设置安装参数,如数据库存放路径,应该是和redis一样可通过安装时设置路径的)
安装及数据库路径配置步骤:
MongoDB数据库软件安装目录/解压目录,C盘有位置的,可以放到C:\Program Files (x86)\Mongodb-8.0.4\mongodb-win32-x86_64-windows-8.0.4\bin
到上述目录下,路径输入 cmd 运行命令行
mongod -dbpath I:\local-MongoDB\data
如将数据库的存储路径,改设置为 I:\local-MongoDB\data
自此,完成数据库服务端软件安装和数据库路径设置
- 启动MongoDB服务端
在数据库软件安装目录/解压目录路径下,cmd命令行中,执行 mongod 是运行的服务端 (本质上是运行服务端程序 mongod.exe)
- 启动MongoDB客户端去连接
低于5.0.14的版本:同目录下 执行
mongo
启动客户端去连接新版本:
- 到https://www.mongodb.com/try/download/shell 下载客户端,解压
- 再在客户端目录下,cmd运行
mongosh
启动客户端去连接数据库(本质上是运行客户端程序 mongosh.exe)
- 留意以下备注
备注:
首次连接过以后,数据库目录下就有数据了
为了方便后续方便使用 mongod 命令,包括启动服务端和客户端,可以将两个端的 bin 目录,都配置到环境变量 Path 中(设置>高级系统设置>环境变量)
千万不要选中服务端窗口的内容
,选中会停止服务;可以将窗口聚焦切换回服务端窗口上,再敲回车
,取消选中,恢复数据库服务;客户端窗口不限制,可以选中MongoDB服务的默认端口是:27017
数据库有自己独立的通信协议,不能直接用http服务去请求MongoDB服务的端口
MongoDB 命令行交互使用
命令行交互一般是学习数据库的第一步,不过这些命令在后续用的比较少,所以大家了解即可
- 操作与JavaScript语法相似,类似方法调用
数据库命令
- 显示所有的数据库
- 查看当前服务下有哪些数据库
show dbs
- 切换到指定的数据库,如果数据库不存在会自动创建数据库
- 执行命令后,显示
switched to db 数据库名字
,表明当前命令行,已经切换到了指定的数据库下操作 - 刚新创建的数据库是空的,在show dbs下看不到
- 在数据库下建表/集合后,就能看到
use 数据库名称
- 执行命令后,显示
- 显示命令行当前所操作/所在的数据库
- 操作数据库前必用,先确认当前命令行是在操作数据库下的哪张表/集合
db
- 删除当前数据库
# 将命令行当前操作的数据库,切换为需要删除的数据库 use 数据库名称 # 操作删除 db.dropDatabase() # 对象.方法.传参()
集合命令/表命令
- 相关集合/表的命令,在操作前,必先确认当前命令行在哪个数据库下
- 创建集合/建表
# 对象.方法.传参() db.createCollection('集合/表名称')
- 显示当前数据库中的所有集合/数据表
show collections
- 删除某个集合/数据表
- 提示true表明已删除成功/已完成操作
db.集合名称.drop()
- 重命名集合/数据表
db.集合名称.renameCollection('新的集合名称')
文档命令
- 相关文档的命令,在操作前,必先确认:
- 当前命令行在哪个数据库下,可以查看下集合/表的情况确认
- 当前数据库服务是处于打开状态
- 插入文档
- 插入的文档数据,是以对象形式,即
{键名1:键值1,key2:value2}
的形式插入
db.集合名称.insert(文档对象)
- 查询文档
- 查询条件为空/即直接调用
db.集合名称.find()
时,为列出整个表 - 传入查询条件时,传入的为一个对象,即
{字段/键名:键值}
_id 是 mongodb 自动生成的唯一编号,用来唯一标识文档
db.集合名称.find(查询条件)
- 查询条件为空/即直接调用
- 更新文档
- update的第二个参数,需要为修改后的完整的文档数据,将会覆盖性地修改第一个参数查询的标的文档
- 第二个参数,可以简写为
{$set:{要更改的字段/键名:更改后键值}}
- 即
{$set:{要更改的数据对象}}
,其中$set
即合法的属性名($
对应JavaScript中的合法标识符)
db.集合名称.update(查询条件, 新的文档的完整数据) db.集合名称.update({name: '张三'}, {name: '张三', age: 19}) db.集合名称.update({name: '张三'}, {$set:{要更改的字段/键名:更改后键值}}) db.集合名称.update({name: '张三'}, {$set:{age: 19}})
- 删除文档
- 查询条件/删除的标的数据,需要是一个
{数据对象}
,即{要删除的字段/键名:及键值}
db.集合名称.remove(查询条件/删除的标的数据)
- 查询条件/删除的标的数据,需要是一个
数据库操作的应用场景
- 新增的应用场景
- 用户注册(帐号注册)
- 发布视频
- 发布商品
- 发朋友圈
- 发评论
- 发微博
- 发弹幕
- ...等等其他
- 删除的应用场景
- 删除评论
- 删除商品
- 删除文章
- 删除视频
- 删除微博
- ...等等其他
删除操作的应用重点:
- 用户在前台的逻辑感知,未必与实际后台数据库的操作逻辑一致
- 在前台感知的删除,在后台真正实现的逻辑,可能是伪删除
- 伪删除逻辑:
- 后台数据库,不将数据从数据库真正删掉,而是将标的数据作二次标记
- 伪删除原理:
- 比如给要删除的标的数据,新增一个属性/类名标记,例如 is-deleted:true 之类
- 当标记属性/类名,对应属性值为true时,前台感知是删了,后台实际逻辑是隐藏
- 伪删除逻辑实现:
- 内容/文档实际上还是在数据库中
- 当读取数据库时,遇到is-deleted:true则不将数据读取到前台,前台感知已删除;
- 当读取数据库时,遇到is-deleted:false时才读取,此时前台感知数据恢复
- 伪删除的意义:
- 积极意义:保留历史数据,防止误操作,提供数据恢复
- 消极意义:违背用户实际操作意愿,违背用户感知
- 更新的应用场景
- 更新个人信息
- 修改商品价格
- 修改文章内容
- ...等等其他
- 查询的应用场景
- 商品列表
- 视频列表
- 朋友圈列表
- 微博列表
- 搜索功能
- ...等等其他
Mongoose
介绍及作用
定义:
- Mongoose 是一个对象文档模型库
- 官网 http://www.mongoosejs.net/
可以理解为:
- Mongoose 是一个封装好的工具包,可以通过npm下载安装,加到项目依赖中
- 在项目主逻辑业务代码中,通过引入Mongoose,实现程序数据与数据库的交互,实现自动化操作数据库的增删改查
库:
- 定义:就是一个工具,一个封装好的工具包
- 举例:运行库
- 就是运行条件的工具包,提供运行环境
- 即在运行标的软件前,需要先将这些前提工具包/依赖安装好
作用:
- 方便在项目中,通过使用代码,操作 mongodb 数据库
- 通过代码程序实现自动化操作数据库
- 取代原有通过MongoDB客户端+客户端终端下敲命令操作服务端数据库的手动交互操作过程
- 方便在项目中,通过使用代码,操作 mongodb 数据库
Mongoose 使用流程
大致流程:
- 装包,通过变量赋予引包导入
- 连接数据库服务,变量.connect()方法中,传递服务的url
- 服务的url包括:协议名称+地址+端口号+操作的数据库的名称
- 协议名称mongodb,端口号根据实际情况来写,默认是27017可以不写,路径上在端口号后面拼接操作的数据库的名称
- 通过设置回调函数,实现相关数据库操作的功能提供
- 在回调函数中提供相应功能
- 设置连接回调mongoose.connection.on('open', () => {功能占位})
- 设置出错回调mongoose.connection.on('error', () => {功能占位})
- 关闭链接回调mongoose.connection.on('close', () => {功能占位})
备注:
- 在电脑上学习时,可以将数据库服务端的启动命令,注册成Windows服务
装包
# 初始化项目npm
npm init
# 装包
npm i mongoose
- 业务逻辑代码中使用
- 补充说明1:设置 strictQuery 为 true,以去掉报警提示
- mongoose.connection设置连接回调时,建议将.on改为.once,设置事件回调函数只执行一次
- 可以理解为:.once就是实现前台用户操作成功的那一次,而.on是每次实际连接实现时就会触发,例如断线重连的场景,也会触发
//1. 安装 mongoose
//2. 导入 mongoose
const mongoose = require('mongoose');
//设置 strictQuery 为 true
mongoose.set('strictQuery', true);
//3. 连接 mongodb 服务 数据库的名称
mongoose.connect('mongodb://127.0.0.1:27017/bilibili');
//4. 设置回调
// 设置连接成功的回调 once 一次 事件回调函数只执行一次
mongoose.connection.once('open', () => {
console.log('连接成功');
// 比如说,一些监听业务,建立连接时一次性执行的逻辑,不应该每次掉线重连都去跑一次
// app.listen(8080);
});
// 设置连接错误的回调
mongoose.connection.on('error', () => {
console.log('连接失败');
});
//设置连接关闭的回调
mongoose.connection.on('close', () => {
console.log('连接关闭');
});
//关闭 mongodb 的连接,以触发数据库链接关闭回调
// setTimeout(() => {
// mongoose.disconnect();
// }, 2000)
Mongoose 插入文档
连接成功后的执行逻辑,应放到连接的回调函数中
let BookSchema = new mongoose.Schema({})理解为创建文档结构对象
- Schema意为模式
- 约束集合中文档中的属性,以及属性值的类型(即:约束每份数据的键值类型)
- 理解为约束表的结构
let BookModel = mongoose.model('books', BookSchema)创建模型对象
- 第一个参数是集合/表的名称
- mongoose 会使用集合名称的复数, 创建集合
- 第二个参数BookSchema结构对象
- BookModel模型对象是对文档操作的封装对象,通过
BookModel.操作方法
,实现调用模型对象中的方法,完成文档的增删改查 - 理解为通过调用
模型对象.操作方法
,完成对表的操作
- 第一个参数是集合/表的名称
注意:
- mongoose从6.0.0开始,create不再接收回调函数作为参数,而是会返回一个promise对象
- 需要用.then和.catch来处理成功和失败的情况
- 可以使用async和await处理异步
//1. 安装 mongoose
//2. 导入 mongoose
const mongoose = require('mongoose');
//设置 strictQuery 为 true
mongoose.set('strictQuery', true);
//3. 连接 mongodb 服务 数据库的名称
mongoose.connect('mongodb://127.0.0.1:27017/bilibili');
//4. 设置回调
// 设置连接成功的回调 once 一次 事件回调函数只执行一次
mongoose.connection.once('open', () => {
console.log('连接成功');
// 比如说,一些监听业务,建立连接时一次性执行的逻辑,不应该每次掉线重连都去跑一次
// app.listen(8080);
//5. 创建文档的结构对象
//设置集合中 文档的属性 以及 属性值的类型
let BookSchema = new mongoose.Schema({
name: String, //字符串
author: String, //字符串
price: Number //数字类型
});
//6. 创建模型对象 对文档操作的封装对象
let BookModel = mongoose.model('books', BookSchema);
//7. 新增
BookModel.create({
name: '西游记',
author: '吴承恩',
price: 19.9
}, (err, data) => {
//判断是否有错误
if (err) {
console.log(err);
return;
}
//如果没有出错, 则输出插入后的文档对象
console.log(data);
//8. 关闭数据库连接 (项目运行过程中, 不会添加该代码)
mongoose.disconnect();
});
});
// 设置连接错误的回调
mongoose.connection.on('error', () => {
console.log('连接失败');
});
//设置连接关闭的回调
mongoose.connection.on('close', () => {
console.log('连接关闭');
});
//关闭 mongodb 的连接,以触发数据库链接关闭回调
// setTimeout(() => {
// mongoose.disconnect();
// }, 2000)
Mongoose 字段类型
- Mongoose 字段类型:文档属性值类型
- 设置了文档属性值类型后,需要设置对应类型的数据信息,数据信息的类型不对会报错
- 如果在设置数据信息时,属性名写错,该属性会被忽略(该属性以及属性值消失)
文档结构可选的常用字段类型列表
类型 | 描述 |
---|---|
String | 字符串 |
Number | 数字 |
Boolean | 布尔值 |
Array | 数组,也可以使用 [] 来标识 |
Date | 日期 |
Buffer | Buffer 对象 |
Mixed | 混合类型,任意类型,需要使用 mongoose.Schema.Types.Mixed 指定 |
ObjectId | 对象 ID,需要使用 mongoose.Schema.Types.ObjectId 指定 |
Decimal128 | 高精度数字,需要使用 mongoose.Schema.Types.Decimal128 指定 |
Buffer类型: 属性值可以是Buffer 对象,可以将文件的二进制内容数据,存入数据库,如图片视频等
- 使用不多,因为通常静态资源放在静态资源文件夹下,如public,然后再将资源的url路径,以字符串形式存入数据库,不直接通过buffer存入数据库
Mixed类型:混合类型,任意类型,赋值文档属性值类型时,不受类型约束,但在设置时需要通过
mongoose.Schema.Types.Mixed
指定,指定后,可以存入任意类型的数据信息ObjectId类型:将属性的值设置为id类型,且必须是文档id
- 一般用于外键(将另一个文档id存入此文档,将两个文档产生关联,当使用另一个文档时,通过id找到另一个文档,取出内容值),作为联合查询
Decimal128:高精度数字类型,对数字精度要求高时使用
常用文档字段类型演示
//1. 安装 mongoose
//2. 导入 mongoose
const mongoose = require('mongoose');
//设置 strictQuery 为 true
mongoose.set('strictQuery', true);
//3. 连接 mongodb 服务 数据库的名称
mongoose.connect('mongodb://127.0.0.1:27017/bilibili');
//4. 设置回调
// 设置连接成功的回调 once 一次 事件回调函数只执行一次
mongoose.connection.once('open', () => {
console.log('连接成功');
// 比如说,一些监听业务,建立连接时一次性执行的逻辑,不应该每次掉线重连都去跑一次
// app.listen(8080);
//5. 创建文档的结构对象
//设置集合中 文档的属性 以及 属性值的类型
// 常用文档字段类型属性
let BookSchema = new mongoose.Schema({
name: String, //字符串
author: String, //字符串
price: Number //数字类型
is_hot: Boolean,
tags: Array,
pub_time: Date,
test: mongoose.Schema.Types.Mixed
test: mongoose.Schema.Types.ObjectId // 文档 ID
});
//6. 创建模型对象 对文档操作的封装对象
let BookModel = mongoose.model('books', BookSchema);
//7. 新增
BookModel.create({
name: '西游记',
author: '吴承恩',
price: 19.9
is_hot: true,
// 写错属性名会被忽略,如以下,运行后,前次加的属性以及属性值is_hot:true会直接消失
// is_hotxxx: true,
tags: ['鬼怪', '励志', '社会'],
pub_time: new Date(),
test: new Date()
}, (err, data) => {
//判断是否有错误
if (err) {
console.log(err);
return;
}
//如果没有出错, 则输出插入后的文档对象
console.log(data);
//8. 关闭数据库连接 (项目运行过程中, 不会添加该代码)
mongoose.disconnect();
});
});
// 设置连接错误的回调
mongoose.connection.on('error', () => {
console.log('连接失败');
});
//设置连接关闭的回调
mongoose.connection.on('close', () => {
console.log('连接关闭');
});
//关闭 mongodb 的连接,以触发数据库链接关闭回调
// setTimeout(() => {
// mongoose.disconnect();
// }, 2000)
Mongoose 字段值验证
Mongoose 字段值验证:对文档属性的值作校验
- 配置方式:将文档的属性类型,配置为对象,在对象中配置字段值验证
- 如果检测的值通过,则放行,插入数据库
- 如果检测的值不合法,则报错,禁止插入数据库
- Mongoose 有一些内建验证器,可以对字段值进行验证
属性类型对象{}中,配置字段值验证:
- required: true // 设置必填项
- default: '匿名' // 设置默认值
- enum: ['男', '女'] // 设置枚举值,要求属性的值,必须是给定的值当中/给定数组中的
- unique: true // 设置唯一值,唯一索引,不能产生重复的
- 设置唯一值需要
重建集合
才能有效果
- 设置唯一值需要
- 永远不要相信用户的输入
1 必填项
title: {
type: String,
required: true // 设置必填项
},
2 默认值
author: {
type: String,
default: '匿名' // 设置默认值
},
3 枚举值
gender: {
type: String,
enum: ['男', '女'] // 设置的值必须是数组中的
},
4 唯一值
username: {
type: String,
unique: true // 设置唯一值
},
unique 需要
重建集合
才能有效果
永远不要相信用户的输入
- 设置字段值验证
//1. 安装 mongoose
//2. 导入 mongoose
const mongoose = require('mongoose');
//设置 strictQuery 为 true
mongoose.set('strictQuery', true);
//3. 连接 mongodb 服务 数据库的名称
mongoose.connect('mongodb://127.0.0.1:27017/bilibili');
//4. 设置回调
// 设置连接成功的回调 once 一次 事件回调函数只执行一次
mongoose.connection.once('open', () => {
console.log('连接成功');
// 比如说,一些监听业务,建立连接时一次性执行的逻辑,不应该每次掉线重连都去跑一次
// app.listen(8080);
//5. 创建文档的结构对象
//设置集合中 文档的属性 以及 属性值的类型
// 设置字段值验证,改为对象类型
let BookSchema = new mongoose.Schema({
name: {
type: String, //字符串
required: true, // 设置必填项,表明该属性必须不为空
unique: true// 设置为独一无二的
},
author: {
type: String, //字符串
default: '匿名'
},
price: Number //数字类型
is_hot: Boolean,
//类型
style: {
type: String,
//枚举
enum: ['言情','城市','志怪','恐怖']
},
pub_time: Date,
test: mongoose.Schema.Types.Mixed
test: mongoose.Schema.Types.ObjectId // 文档 ID
});
//6. 创建模型对象 对文档操作的封装对象
let BookModel = mongoose.model('books', BookSchema);
//7. 新增
BookModel.create({
name: '西游记',
// author: '吴承恩', 注释后会以默认值插入
price: 19.9,
style: '志怪',
pub_time: new Date(),
test: new Date()
}, (err, data) => {
//判断是否有错误
if (err) {
console.log(err);
return;
}
//如果没有出错, 则输出插入后的文档对象
console.log(data);
//8. 关闭数据库连接 (项目运行过程中, 不会添加该代码)
mongoose.disconnect();
});
});
// 设置连接错误的回调
mongoose.connection.on('error', () => {
console.log('连接失败');
});
//设置连接关闭的回调
mongoose.connection.on('close', () => {
console.log('连接关闭');
});
//关闭 mongodb 的连接,以触发数据库链接关闭回调
// setTimeout(() => {
// mongoose.disconnect();
// }, 2000)
Mongoose CURD
数据库的基本操作包括四个,增加(create),删除(delete),修改(update),查(read)
增加/插入单条
SongModel.create({
title: '给我一首歌的时间',
author: 'Jay'
}, function (err, data) {
//错误
console.log(err);
//插入后的数据对象
console.log(data);
});
- 批量插入
//1.引入mongoose
const mongoose = require('mongoose');
//2.链接mongodb数据库 connect 连接
mongoose.connect('mongodb://127.0.0.1:27017/project');
//3.设置连接的回调
mongoose.connection.on('open', () => {
//4.声明文档结构
const PhoneSchema = new mongoose.Schema({
brand: String,
color: String,
price: Number,
tags: Array
})
//6.创建模型对象
const PhoneModel = mongoose.model('phone', PhoneSchema);
PhoneModel.insertMany([
{
brand: '华为',
color: '灰色',
price: 2399,
tags: ['电量大', '屏幕大', '信号好']
},
{
brand: '小米',
color: '白色',
price: 2099,
tags: ['电量大', '屏幕大', '信号好']
}
], (err, data) => {
if (err) throw err;
console.log('写入成功');
mongoose.connection.close();
});
});
Mongoose 删除文档
mongoose 会使用集合名称的复数, 创建集合
语法:
BookModel.deleteOne({单条唯一条件}, 回调函数)
- 回调函数新的语法改为.then(data,err)
- 批量删除语法为
BookModel.deleteMany({多条普适条件}, 回调函数)
执行删除文档命令后,回调函数返回的data值是一个对象
- deletedcount:删除统计值,显示删除的数据条数
举例删除一条数据
SongModel.deleteOne({ _id: '5dd65f32be6401035cb5b1ed' }, function (err) {
if (err) throw err;
console.log('删除成功');
mongoose.connection.close();
});
- 举例批量删除
SongModel.deleteMany({ author: 'Jay' }, function (err) {
if (err) throw err;
console.log('删除成功');
mongoose.connection.close();
});
- 删除文档案例
//1. 安装 mongoose
//2. 导入 mongoose
const mongoose = require('mongoose');
//设置 strictQuery 为 true
mongoose.set('strictQuery', true);
//3. 连接 mongodb 服务 数据库的名称
mongoose.connect('mongodb://127.0.0.1:27017/bilibili');
//4. 设置回调
// 设置连接成功的回调 once 一次 事件回调函数只执行一次
mongoose.connection.once('open', () => {
//5. 创建文档的结构对象
//设置集合中文档的属性以及属性值的类型
let BookSchema = new mongoose.Schema({
name: String,
author: String,
price: Number,
is_hot: Boolean
});
//6. 创建模型对象 对文档操作的封装对象 mongoose 会使用集合名称的复数, 创建集合
let BookModel = mongoose.model('novel', BookSchema);
//7. 删除一条
BookModel.deleteOne({_id: '63f34af50cf203761ede1892'}, (err, data) => {
//判断
if(err){
console.log('删除失败~~~');
return;
}
//输出 data
console.log(data);
});
//批量删除
BookModel.deleteMany({is_hot: false}, (err, data) => {
//判断
if(err){
console.log('删除失败~~~');
return;
}
//输出 data
console.log(data);
});
});
// 设置连接错误的回调
mongoose.connection.on('error', () => {
console.log('连接失败');
});
//设置连接关闭的回调
mongoose.connection.on('close', () => {
console.log('连接关闭');
});
Mongoose 更新文档
语法:
BookModel.updateOne({单条唯一条件}, {更新后的新内容}, 回调函数)
- 批量更新语法为
BookModel.updateMany({多条普适条件}, {更新后的新内容}, 回调函数)
- 只会更新与新内容定义的同名属性,其他属性不会有影响
- modifiedcount:更新统计值,显示更新的数据条数
- 新版本需要使用promise去操作
与命令行不同,更新时,不会把原来的数据覆盖掉,而是只更新你传入的字段。
举例更新一条数据
SongModel.updateOne({ author: 'JJ Lin' }, { author: '林俊杰' }, function (err) {
if (err) throw err;
mongoose.connection.close();
});
- 举例批量更新数据
SongModel.updateMany({ author: 'Leehom Wang' }, { author: '王力宏' }, function (err) {
if (err) throw err;
mongoose.connection.close();
});
- 更新文档案例
//1. 安装 mongoose
//2. 导入 mongoose
const mongoose = require('mongoose');
//设置 strictQuery 为 true
mongoose.set('strictQuery', true);
//3. 连接 mongodb 服务 数据库的名称
mongoose.connect('mongodb://127.0.0.1:27017/bilibili');
//4. 设置回调
// 设置连接成功的回调 once 一次 事件回调函数只执行一次
mongoose.connection.once('open', () => {
//5. 创建文档的结构对象
//设置集合中文档的属性以及属性值的类型
let BookSchema = new mongoose.Schema({
name: String,
author: String,
price: Number,
is_hot: Boolean
});
//6. 创建模型对象 对文档操作的封装对象 mongoose 会使用集合名称的复数, 创建集合
let BookModel = mongoose.model('novel', BookSchema);
//7. 更新文档 更新一条
BookModel.updateOne({name: '红楼梦'}, {price: 9.9}, (err, data) => {
//判断 err
if(err){
console.log('更新失败~~');
return;
}
//输出 data
console.log(data);
});
//批量更新
BookModel.updateMany({author: '余华'}, {is_hot: true}, (err, data) => {
//判断 err
if(err){
console.log('更新失败~~');
return;
}
//输出 data
console.log(data);
});
});
// 设置连接错误的回调
mongoose.connection.on('error', () => {
console.log('连接失败');
});
//设置连接关闭的回调
mongoose.connection.on('close', () => {
console.log('连接关闭');
});
Mongoose 读取/查询文档
语法:
BookModel.findOne({单条唯一条件}, 回调函数)
- 根据id读取
BookModel.findById('id编号', 回调函数)
- 批量读取语法为
BookModel.find({多条普适条件}, 回调函数)
- 批量读取语法如果没有条件,可以直接写
BookModel.find(回调函数)
- 回调函数中的data保留了读取后出来的字段
新版本下,使用
.then((data)=>{})
,.catch((err)=>{})
举例查询一条数据
SongModel.findOne({ author: '王力宏' }, function (err, data) {
if (err) throw err;
console.log(data);
mongoose.connection.close();
});
//根据 id 查询数据
SongModel.findById('5dd662b5381fc316b44ce167', function (err, data) {
if (err) throw err;
console.log(data);
mongoose.connection.close();
});
- 举例批量查询数据
//不加条件查询
SongModel.find(function (err, data) {
if (err) throw err;
console.log(data);
mongoose.connection.close();
});
//加条件查询
SongModel.find({ author: '王力宏' }, function (err, data) {
if (err) throw err;
console.log(data);
mongoose.connection.close();
});
- 读取/查询文档案例
//1. 安装 mongoose
//2. 导入 mongoose
const mongoose = require('mongoose');
//设置 strictQuery 为 true
mongoose.set('strictQuery', true);
//3. 连接 mongodb 服务 数据库的名称
mongoose.connect('mongodb://127.0.0.1:27017/bilibili');
//4. 设置回调
// 设置连接成功的回调 once 一次 事件回调函数只执行一次
mongoose.connection.once('open', () => {
//5. 创建文档的结构对象
//设置集合中文档的属性以及属性值的类型
let BookSchema = new mongoose.Schema({
name: String,
author: String,
price: Number,
is_hot: Boolean
});
//6. 创建模型对象 对文档操作的封装对象 mongoose 会使用集合名称的复数, 创建集合
let BookModel = mongoose.model('novel', BookSchema);
//7. 读取单条
BookModel.findOne({name: '狂飙'}, (err, data) => {
if(err){
console.log('读取失败~~~');
return;
}
//输出 data 变量的值
console.log(data);
})
//根据 ID 获取文档
BookModel.findById('63f34af50cf203761ede1896', (err, data) => {
if (err) {
console.log('读取失败~~~');
return;
}
//输出 data 变量的值
console.log(data);
})
//批量获取
BookModel.find({ author: '余华' }, (err, data) => {
if (err) {
console.log('读取失败~~~');
return;
}
//输出 data 变量的值
console.log(data);
});
//读取所有
BookModel.find((err, data) => {
if (err) {
console.log('读取失败~~~');
return;
}
//输出 data 变量的值
console.log(data);
})
});
// 设置连接错误的回调
mongoose.connection.on('error', () => {
console.log('连接失败');
});
//设置连接关闭的回调
mongoose.connection.on('close', () => {
console.log('连接关闭');
});
Mongoose 条件控制
Mongoose 条件控制:如何去设置查询文档的条件
- 背景:此前使用
{键名:键值}
的方式去查询,也就是=
键名键值等于条件
- 背景:此前使用
语法:
- 单个限制条件
BookModel.find({字段名称/键名:{条件控制:键值限制值}}, 回调函数)
- 多个限制条件
BookModel.find({ 条件控制:[{字段名称/键名1:键值限制值1},{字段名称/键名2:键值限制值2}] }, 回调函数)
- 写清晰一点也就是
BookModel.find({ 条件控制:[{条件1},{条件2}] }, 回调函数)
- 写清晰一点也就是
- 正则匹配
BookModel.find({ 字段名称/键名:正则表达式 }, 回调函数)
- 另一种正则匹配
BookModel.find({ 字段名称/键名:new RegExp('可通过变量传值键值限制值') }, 回调函数)
,支持通过变量传值键值限制值 - 回调函数中的data保留了符合条件控制后的字段
- 单个限制条件
能在数据库里面查到的,就不要使用JavaScript的语法来过滤,非常影响接口的响应速度的
运算符
- 通过运算符设置查询条件
- 在 mongodb 不能
> < >= <= !==
等运算符,需要使用替代符号 >
使用$gt
<
使用$lt
>=
使用$gte
<=
使用$lte
!==
使用$ne
- 在 mongodb 不能
通过运算符设置查询条件举例
db.students.find({ id: { $gt: 3 } }); //id号比3大的所有的记录
逻辑运算
- 通过逻辑运算设置查询条件
$or
逻辑或的情况$and
逻辑与的情况
通过逻辑运算设置查询条件举例
// 逻辑或的情况
db.students.find({ $or: [{ age: 18 }, { age: 24 }] });
// 逻辑与的情况
db.students.find({ $and: [{price: {$gt: 30}}, {price: {$lt: 70}}] });
正则匹配
- 通过正则匹配设置查询条件
- 条件中可以直接使用 JS 的正则语法
- 通过正则可以进行模糊查询
通过正则匹配设置查询条件举例
db.students.find({ name: /imissyou/ });
条件控制设置
//1. 安装 mongoose
//2. 导入 mongoose
const mongoose = require('mongoose');
//设置 strictQuery 为 true
mongoose.set('strictQuery', true);
//3. 连接 mongodb 服务 数据库的名称
mongoose.connect('mongodb://127.0.0.1:27017/bilibili');
//4. 设置回调
// 设置连接成功的回调 once 一次 事件回调函数只执行一次
mongoose.connection.once('open', () => {
//5. 创建文档的结构对象
//设置集合中文档的属性以及属性值的类型
let BookSchema = new mongoose.Schema({
name: String,
author: String,
price: Number,
is_hot: Boolean
});
//6. 创建模型对象 对文档操作的封装对象 mongoose 会使用集合名称的复数, 创建集合
let BookModel = mongoose.model('novel', BookSchema);
//价格小于 20 的图书
BookModel.find({price: {$lt: 20}}, (err, data) => {
if(err) {
console.log('读取失败~~');
return;
}
console.log(data);
})
//曹雪芹 或者 余华的书
BookModel.find({$or: [{author: '曹雪芹'}, {author: '余华'}]}, (err, data) => {
if (err) {
console.log('读取失败~~');
return;
}
console.log(data);
})
//价格大于 30 且小于 70
BookModel.find({$and: [{price: {$gt: 30}}, {price: {$lt: 70}}]}, (err, data) => {
if (err) {
console.log('读取失败~~');
return;
}
console.log(data);
})
//模糊查询:正则表达式, 搜索书籍名称中带有 `三` 的图书
BookModel.find({name: /三/}, (err, data) => {
if (err) {
console.log('读取失败~~');
return;
}
console.log(data);
})
// 另一种正则表达式
BookModel.find({name: new RegExp('三')}, (err, data) => {
if (err) {
console.log('读取失败~~');
return;
}
console.log(data);
})
});
// 设置连接错误的回调
mongoose.connection.on('error', () => {
console.log('连接失败');
});
//设置连接关闭的回调
mongoose.connection.on('close', () => {
console.log('连接关闭');
});
Mongoose 个性化读取
- 字段筛选、排序、截取的语法,可以叠加/连贯使用
字段筛选
字段筛选定义:
- 背景:读取时,返回了集合里面的所有字段/属性
- 定义:按照给定的筛选条件,去控制只读取符合某些相应条件的字段/属性
- 理解:从表中筛选出符合条件的字段/属性再作读取,不全部读取,提高读取效率
字段筛选语法:
find().select({ 需要读取字段名称/属性名1:条件控制0或1,需要读取字段名称/属性名2:条件控制0或1 }).exec(回调函数)
- 0:不要的字段
- 1:需要的字段
- 回调函数中的data保留了筛选后的字段
//0:不要的字段
//1:要的字段
SongModel.find().select({ _id: 0, title: 1 }).exec(function (err, data) {
if (err) throw err;
console.log(data);
mongoose.connection.close();
});
数据排序
数据排序:
- 定义:在字段筛选的基础上,再对结果作排序
- 应用场景:商城类网站,按照条件排序等场景
数据排序语法:
find().sort({需要排序字段名称/属性名: 条件控制1或-1}).exec(回调函数)
- 1:升序
- -1:倒序
- 可以叠加/连贯使用,如
find().select({ 需要读取字段名称/属性名1:条件控制0或1,需要读取字段名称/属性名2:条件控制0或1 }).sort({需要排序字段名称/属性名: 条件控制1或-1}).exec(回调函数)
,在回调函数中的data,保留了筛选+排序后的字段
//sort 排序
//1:升序
//-1:倒序
SongModel.find().sort({ hot: 1 }).exec(function (err, data) {
if (err) throw err;
console.log(data);
mongoose.connection.close();
});
数据截取
数据截取:
- 定义:在数据排序和截取的基础上,执行跳过或限定,去截取一部分数据作为结果,放入回调函数的data(范围值)
- 应用场景:搜索结果分页;返回结果数据量大的场景需要按截取返回,提高效率
数据截取:
find().排序.limit(数量值)
:在排序的基础上截取数量值find().排序.skip(数量值1).limit(数量值2)
:在排序的基础上,跳过数量值1,再去截取数量值2- 可以叠加/连贯使用
//skip 跳过 limit 限定
SongModel.find().skip(10).limit(10).exec(function (err, data) {
if (err) throw err;
console.log(data);
mongoose.connection.close();
});
字段筛选+排序+截取设置
//1. 安装 mongoose
//2. 导入 mongoose
const mongoose = require('mongoose');
//设置 strictQuery 为 true
mongoose.set('strictQuery', true);
//3. 连接 mongodb 服务 数据库的名称
mongoose.connect('mongodb://127.0.0.1:27017/bilibili');
//4. 设置回调
// 设置连接成功的回调 once 一次 事件回调函数只执行一次
mongoose.connection.once('open', () => {
//5. 创建文档的结构对象
//设置集合中文档的属性以及属性值的类型
let BookSchema = new mongoose.Schema({
name: String,
author: String,
price: Number,
is_hot: Boolean
});
//6. 创建模型对象 对文档操作的封装对象 mongoose 会使用集合名称的复数, 创建集合
let BookModel = mongoose.model('novel', BookSchema);
//7. 设置字段
BookModel.find().select({name: 1, author: 1, _id: 0}).exec((err, data) => {
if(err) {
console.log('查询失败~~');
return;
}
console.log(data);
})
//数据排序
BookModel.find().select({name: 1, price: 1, _id: 0}).sort({price: -1}).exec((err, data) => {
if(err) {
console.log('查询失败~~');
return;
}
console.log(data);
})
//数据的截取
BookModel.find()
.select({name: 1, price: 1, _id: 0})
.sort({price: -1})
.skip(3)
.limit(3)
.exec((err, data) => {
if(err) {
console.log('查询失败~~');
return;
}
console.log(data);
})
});
// 设置连接错误的回调
mongoose.connection.on('error', () => {
console.log('连接失败');
});
//设置连接关闭的回调
mongoose.connection.on('close', () => {
console.log('连接关闭');
});
不可以。
Mongoose 中文档操作的方法名称是固定的,不能自定义命名。
Mongoose 提供了一组预定义的方法来操作文档,包括:
create()
save()
update()
updateOne()
updateMany()
deleteOne()
deleteMany()
findById()
findByIdAndUpdate()
findByIdAndDelete()
findOne()
findOneAndDelete()
findOneAndUpdate()
这些方法名称是固定的,并且不能更改。这是因为 Mongoose 使用这些方法名称来识别要执行的操作。
如果你想执行自定义操作,你可以使用 aggregate() 方法。aggregate() 方法允许你使用管道操作符来执行复杂的数据操作。管道操作符是 Mongoose 提供的一组用于处理数据的函数。
例如,以下代码使用 aggregate() 方法执行自定义操作,将文档中的 name 字段更新为大写:
Copy
const updatedDocument = await MyModel.aggregate([
{
$set: {
name: { $toUpper: "$name" }
}
}
]);
有关 aggregate() 方法的更多信息,请参阅 Mongoose 文档:https://mongoosejs.com/docs/api/aggregate.html
Mongoose 代码模块化
- 对以上所学的文档的操作的方法,在使用时,作拆分+封装代码,方便复用
初步模块化:连接+回调拆分
将strictQuery设置、连接 mongodb 服务、设置回调,拆分抽出封装
- 通过包在函数中,再对外暴露
module.exports = function (success, error) {连接相关操作}
- 传入两个参数供回调:(success,error)
- 将连接相关的操作抽离,通过导入+调用函数+传参的方式,在合适的时机调用执行
- 通过包在函数中,再对外暴露
mongoose模块化项目\db\db.js文件
/**
*
* @param {*} success 数据库连接成功的回调
* @param {*} error 数据库连接失败的回调
*/
module.exports = function (success, error) {
//1. 安装 mongoose
//2. 导入 mongoose
const mongoose = require('mongoose');
//设置 strictQuery 为 true
mongoose.set('strictQuery', true);
//3. 连接 mongodb 服务
mongoose.connect('mongodb://127.0.0.1:27017/bilibili');
//4. 设置回调
// 设置连接成功的回调 once 一次 事件回调函数只执行一次
mongoose.connection.once('open', () => {
success();
});
// 设置连接错误的回调
mongoose.connection.on('error', () => {
error();
});
//设置连接关闭的回调
mongoose.connection.on('close', () => {
console.log('连接关闭');
});
}
- mongoose模块化项目\index.js中导入db.js文件
//导入 db 文件
const db = require('./db/db');
//导入 mongoose
const mongoose = require('mongoose');
// 调用函数
db(
// 传实参success,数据库连接成功以后执行
() => {
//5. 创建文档的结构对象
//设置集合中文档的属性以及属性值的类型
let BookSchema = new mongoose.Schema({
name: String,
author: String,
price: Number
});
//6. 创建模型对象 对文档操作的封装对象
let BookModel = mongoose.model('books', BookSchema);
//7. 新增
BookModel.create({
name: '西游记',
author: '吴承恩',
price: 19.9
}, (err, data) => {
//判断是否有错误
if (err) {
console.log(err);
return;
}
//如果没有出错, 则输出插入后的文档对象
console.log(data);
//8. 关闭数据库连接 (项目运行过程中, 不会添加该代码)
mongoose.disconnect();
});
},
// 传实参error
() => {
console.log('连接失败...');
});
结构和模型拆分
对结构对象和模型创建作封装
- 将 创建文档的结构对象 和 创建模型对象 抽出封装到代码\models目录下BookModel.js
- BookModel.js对外暴露模型对象
module.exports = BookModel;
- 在\index.js中,导入BookModel.js
- BookModel.js对外暴露模型对象
- 后续有新的
创建文档的结构对象
和创建模型对象
,即建-新表
也能类似操作 - 后续只在\index.js的调用函数中,保留数据新增,如
BookModel.create
- 将 创建文档的结构对象 和 创建模型对象 抽出封装到代码\models目录下BookModel.js
抽出结构对象和模型创建,mongoose模块化项目\models\BookModel.js
//导入 mongoose
const mongoose = require('mongoose');
//5.创建文档的结构对象
//设置集合中文档的属性以及属性值的类型
let BookSchema = new mongoose.Schema({
name: String,
author: String,
price: Number
});
//6.创建模型对象 对文档操作的封装对象
let BookModel = mongoose.model('books', BookSchema);
//暴露模型对象
module.exports = BookModel;
- 建新表,mongoose模块化项目\models\MovieModel.js
//导入 mongoose
const mongoose = require('mongoose');
// 创建文档结构
const MovieSchema = new mongoose.Schema({
title: String,
director: String
});
//创建模型对象
const MovieModel = mongoose.model('movie', MovieSchema);
//暴露
module.exports = MovieModel;
- mongoose模块化项目\index.js中,导入抽出的结构对象和模型创建BookModel.js
//导入 db 文件
const db = require('./db/db');
//导入 mongoose
const mongoose = require('mongoose');
//导入 BookModel
const BookModel = require('./models/BookModel');
// 调用函数
db(() => {
//7. 新增
BookModel.create({
name: '西游记',
author: '吴承恩',
price: 19.9
}, (err, data) => {
//判断是否有错误
if(err) {
console.log(err);
return;
}
//如果没有出错, 则输出插入后的文档对象
console.log(data);
//8. 关闭数据库连接 (项目运行过程中, 不会添加该代码)
mongoose.disconnect();
});
}, () => {
console.log('连接失败...');
});
- 导入新表
//导入 db
const db = require('./db/db');
//导入 MovieModel
const MovieModel = require('./models/MovieModel')
//调用函数
db(() => {
//电影的模型对象
MovieModel.create({title: '让子弹飞', director: '姜文'}, (err ,data) => {
if(err){
console.log('插入失败~~');
return;
}
console.log('插入成功');
})
})
- 流程总结
- 导入db
- 创建模型&导入模型
- 使用模型对象作操作
二次优化
目标1:
- 调用db时,省略第二个err传参,只传成功参数
解决:
- 在index中,取消第二个参数的调用
- 改为在db中,将第二个参数设为默认值
目标2:
- 抽离出数据库信息,作为单独的配置文件,方便变化时更改
解决:
- 创建单独的config文件,将抽离出的数据库信息,放在mongoose模块化项目\config\config.js中
- 回到db.js文件中,导入使用
目标1:将err参数设置为db的默认值,mongoose模块化项目\db\db.js文件
/**
*
* @param {*} success 数据库连接成功的回调
* @param {*} error 数据库连接失败的回调
*/
module.exports = function (success, error) {
//判断 error 为其设置默认值;内部判断如果error 不等于函数时,返回连接失败
if(typeof error !== 'function'){
error = () => {
console.log('连接失败~~~');
}
}
//1. 安装 mongoose
//2. 导入 mongoose
const mongoose = require('mongoose');
//设置 strictQuery 为 true
mongoose.set('strictQuery', true);
//3. 连接 mongodb 服务
mongoose.connect('mongodb://127.0.0.1:27017/bilibili');
//4. 设置回调
// 设置连接成功的回调 once 一次 事件回调函数只执行一次
mongoose.connection.once('open', () => {
success();
});
// 设置连接错误的回调
mongoose.connection.on('error', () => {
error();
});
//设置连接关闭的回调
mongoose.connection.on('close', () => {
console.log('连接关闭');
});
}
- 目标2:抽离出数据库信息,作为单独的配置文件,mongoose模块化项目\config\config.js
// 配置文件
// 做成配置项,包括地址、端口、表名称
module.exports = {
DBHOST: '127.0.0.1',
DBPORT: 27017,
DBNAME: 'bilibili'
}
- 目标2:在db.js中,导入配置文件并使用
/**
*
* @param {*} success 数据库连接成功的回调
* @param {*} error 数据库连接失败的回调
*/
module.exports = function (success, error) {
//判断 error 为其设置默认值;内部判断如果error 不等于函数时,返回连接失败
if(typeof error !== 'function'){
error = () => {
console.log('连接失败~~~');
}
}
//1. 安装 mongoose
//2. 导入 mongoose
const mongoose = require('mongoose');
//导入 配置文件
const {DBHOST, DBPORT, DBNAME} = require('../config/config.js');
//设置 strictQuery 为 true
mongoose.set('strictQuery', true);
//3. 连接 mongodb 服务 数据库的名称
mongoose.connect(`mongodb://${DBHOST}:${DBPORT}/${DBNAME}`);
//4. 设置回调
// 设置连接成功的回调 once 一次 事件回调函数只执行一次
mongoose.connection.once('open', () => {
success();
});
// 设置连接错误的回调
mongoose.connection.on('error', () => {
error();
});
//设置连接关闭的回调
mongoose.connection.on('close', () => {
console.log('连接关闭');
});
}
MongoDB 图形化管理工具
- 背景:
- 此前通过两种方式与 Mongodb 进行交互:
- 手敲命令行
- mongoose自动化代码程序
- 此前通过两种方式与 Mongodb 进行交互:
我们可以使用图形化的管理工具来对 Mongodb 进行交互,这里演示两个图形化工具
- 安装
- Navicat Premium 16 能用在多个类型的数据库中,相当于是 Navicat {
MySQL
+MongoDB
+PostgreSQL
+MariaDB
+SQL Server
+Oracle
}
Navicat操作技巧:
- 在集合/表中,新建文档,可以光标定位到表格末尾,按tab新增,按ctrl+s新增成功
- 数据更改完,丧失焦点,保存一下,则完成更改
图形化工具交互:
- 方便简单
- 较多应用在检查数据时的场景
实战案例-记账本-结合MongoDB数据库操作
在跑http服务之前,引入MongoDB数据库
引入MongoDB数据库步骤:
- 参考 Mongoose 代码模块化,将模块化拆分后的config、db、model三个文件目录,放到需要优化的记账本项目文件夹目录下
- 确认express的入口文件,在记账本项目文件夹/bin/www.js中
- 目的:确保连上db数据库以后,再去跑http服务相关代码
- 操作:导入db函数,调用db函数,将http服务相关代码,放入被调用的 db 回调函数中
引入db数据库,并将http服务相关代码放入被调用的 db 回调函数中
#!/usr/bin/env node
//导入 db 函数
const db = require('../db/db');
//调用 db 函数
db(() => {
/**
* Module dependencies.
*/
var app = require('../app');
var debug = require('debug')('accounts:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
})
创建模型文件
步骤:
- 分析:要借助数据库处理数据,需要先有模型文件(定义模型/定义表)
- 操作:在记账本项目文件夹\models\AccountModel.js中新建模型文件
- 根据需要增加文档字段
- id字段是MongoDB自动生成的,不需要手动加入此字段
创建模型文件,定义文档结构,项目文件夹\models\AccountModel.js
//导入 mongoose
const mongoose = require('mongoose');
//创建文档的结构对象
//设置集合中文档的属性以及属性值的类型
let AccountSchema = new mongoose.Schema({
//标题
title: {
type: String,
required: true
},
//时间,类型是日期对象
time: Date,
//类型
type: {
// 数字类型
type: Number,
// 默认是支出
default: -1
},
//金额
account: {
type: Number,
required: true
},
//备注
remarks: {
type: String
}
});
//创建模型对象 对文档操作的封装对象
let AccountModel = mongoose.model('accounts', AccountSchema);
//暴露模型对象
module.exports = AccountModel;
表单数据插入数据库文档
分析:
- 表单提交的逻辑,通过路由项目文件夹\routes\index.js确认,前面新增记录的请求是发送给了.post路由规则
- 通过打印,确认表单提交的内容(主要是数据属性),与上节定义的模型/表的字段是否对得上
- 打印的日期是一个带-的日期时间字符串,但进入数据库是转为对象存储的,需要注意转换:
2023-02-24
=>new Date()
- 步骤:
- 修改原有的项目文件夹\routes\index.js文件,导入moment工具包
- 在新增记录的路由回调中,通过AccountModel方法&引入模型,实现表单数据存入数据库
- 数据插入成功后可通过可视化软件检查数据库
表单数据插入数据库文档,项目文件夹\routes\index.js
var express = require('express')
var router = express.Router()
//导入 lowdb
const low = require('lowdb')
const FileSync = require('lowdb/adapters/FileSync')
const adapter = new FileSync(__dirname + '/../data/db.json')
//获取 db 对象
const db = low(adapter)
//导入 shortid
const shortid = require('shortid')
//导入 moment
const moment = require('moment')
const AccountModel = require('../models/AccountModel')
//测试
// console.log(moment('2023-02-24').toDate())
//格式化日期对象
// console.log(moment(new Date()).format('YYYY-MM-DD'));
//记账本的列表
router.get('/account', function (req, res, next) {
//获取所有的账单信息
let accounts = db.get('accounts').value();
res.render('list', { accounts: accounts })
})
//添加记录
router.get('/account/create', function (req, res, next) {
res.render('create')
})
//新增记录
router.post('/account', (req, res) => {
// 查看表单数据
// 通过打印,确认表单提交的内容(主要是数据属性),与上节定义的模型/表的字段是否对得上
console.log(req.body)
// 在插入数据库之前也可以先修改时间
// req.body.time = moment(req.body.time).toDate()
//插入数据库
AccountModel.create({
// es6语法,展开req.body所有属性,放入对象中
...req.body,
// 修改其中的 time 属性的值
// 字符串转moment对象,再.toDate()转日期对象
time: moment(req.body.time).toDate()
}, (err, data) => {
if (err) {
// 如果有问题,返回错误的500响应
res.status(500).send('插入失败~~')
return
}
//成功提醒
res.render('success', { msg: '添加成功哦~~~', url: '/account' })
})
})
//删除记录
router.get('/account/:id', (req, res) => {
//获取 params 的 id 参数
let id = req.params.id
//删除
AccountModel.deleteOne({ _id: id }, (err, data) => {
if (err) {
res.status(500).send('删除失败~')
return
}
//提醒
res.render('success', { msg: '删除成功~~~', url: '/account' })
})
})
module.exports = router
读取数据库
分析:
- 数据回显,需要通过读取数据库实现(之前列表的数据,是通过读取json文件,即项目文件夹\data\db.json获取的)需要更换数据源为数据库
步骤:
- 先找到对应的路由规则,在项目文件夹\routes\index.js中,找到.get路由规则
- 通过AccountModel方法,连贯调用排序方法,拿到数据打印确认
- 在页面发请求,回车,浏览器页签显示缓冲圈,显示页面一直在加载,响应结果还没有返回
- 原因是因为,在服务端路由规则中,没有写响应代码,没有成功的返回值,因此页面一直在等待响应,现象是正常的
- 数据打印确认ok后,最后交给ejs的render渲染
res.render('list', { accounts: accounts })
中,键是ejs模板中使用的变量,后面才是数据- 数据是存储在data中,因此修改键值,为:
res.render('list', { accounts: data, moment: moment })
- 同步修改ejs中的时间模板显示格式,以使用moment格式化日期对象回来,以带-形式显示
读取数据库回显,项目文件夹\routes\index.js路由规则中
var express = require('express')
var router = express.Router()
//导入 lowdb
const low = require('lowdb')
const FileSync = require('lowdb/adapters/FileSync')
const adapter = new FileSync(__dirname + '/../data/db.json')
//获取 db 对象
const db = low(adapter)
//导入 shortid
const shortid = require('shortid')
//导入 moment
const moment = require('moment')
const AccountModel = require('../models/AccountModel')
//测试
// console.log(moment('2023-02-24').toDate())
//格式化日期对象
// console.log(moment(new Date()).format('YYYY-MM-DD'));
//记账本的列表
router.get('/account', function (req, res, next) {
//获取所有的账单信息
// let accounts = db.get('accounts').value();
//读取集合信息
AccountModel.find().sort({ time: -1 }).exec((err, data) => {
if (err) {
res.status(500).send('读取失败~~~')
return
}
// 先确认响应成功的data
console.log(data)
//响应成功的提示,交给ejs渲染
res.render('list', { accounts: data, moment: moment })
})
})
//添加记录
router.get('/account/create', function (req, res, next) {
res.render('create')
})
//新增记录
router.post('/account', (req, res) => {
// 查看表单数据
// 通过打印,确认表单提交的内容(主要是数据属性),与上节定义的模型/表的字段是否对得上
console.log(req.body)
// 在插入数据库之前也可以先修改时间
// req.body.time = moment(req.body.time).toDate()
//插入数据库
AccountModel.create({
// es6语法,展开req.body所有属性,放入对象中
...req.body,
// 修改其中的 time 属性的值
// 字符串转moment对象,再.toDate()转日期对象
time: moment(req.body.time).toDate()
}, (err, data) => {
if (err) {
// 如果有问题,返回错误的500响应
res.status(500).send('插入失败~~')
return
}
//成功提醒
res.render('success', { msg: '添加成功哦~~~', url: '/account' })
})
})
//删除记录
router.get('/account/:id', (req, res) => {
//获取 params 的 id 参数
let id = req.params.id
//删除
AccountModel.deleteOne({ _id: id }, (err, data) => {
if (err) {
res.status(500).send('删除失败~')
return
}
//提醒
res.render('success', { msg: '删除成功~~~', url: '/account' })
})
})
module.exports = router
- 同步修改ejs显示模板,项目文件夹\views\list.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css" rel="stylesheet" />
<style>
label {
font-weight: normal;
}
.panel-body .glyphicon-remove {
display: none;
}
.panel-body:hover .glyphicon-remove {
display: inline-block
}
</style>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-xs-12 col-lg-8 col-lg-offset-2">
<div class="row">
<h2 class="col-xs-6">记账本</h2>
<h2 class="col-xs-6 text-right"><a href="/account/create" class="btn btn-primary">添加账单</a></h2>
</div>
<hr />
<div class="accounts">
<% accounts.forEach(item=> { %>
<div class="panel <%= item.type=== -1 ? 'panel-danger' : 'panel-success' %>">
<div class="panel-heading">
<%= moment(item.time).format('YYYY-MM-DD') %>
</div>
<div class="panel-body">
<div class="col-xs-6">
<%= item.title %>
</div>
<div class="col-xs-2 text-center">
<span class="label <%= item.type=== -1 ? 'label-warning' : 'label-success' %>">
<%= item.type===-1 ? '支出' : '收入' %>
</span>
</div>
<div class="col-xs-2 text-right">
<%= item.account %> 元
</div>
<div class="col-xs-2 text-right">
<a class="delBtn" href="/account/<%= item._id %>">
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
</a>
</div>
</div>
</div>
<% }) %>
</div>
</div>
</div>
</div>
</body>
<script>
//获取所有的 delBtn
let delBtns = document.querySelectorAll('.delBtn');
//绑定事件
delBtns.forEach(item => {
item.addEventListener('click', function (e) {
if (confirm('您确定要删除该文档么??')) {
return true;
} else {
//阻止默认行为
e.preventDefault();
}
});
})
</script>
</html>
删除文档
分析:
- 数据删除,需要通过删除数据库中的文档实现(之前列表的数据,是通过删除json文件中的数据,即项目文件夹\data\db.json获取的)需要更换删除操作的数据源为数据库
步骤:
- 先找到对应的路由规则,在项目文件夹\routes\index.js中,找到.get删除记录相关的路由规则
- 在浏览器上,鼠标悬停元素,浏览器左下角有该元素的链接地址显示,确认拿到的文档的id是否正确
- mongoose会往对象中自动添加id属性,id属性与_id的值是一样的
- 通过AccountModel.deleteOne方法操作数据库
- 优化,防止误删除,在ejs中,通过js方法增加个二次确认
- 优化,增加添加按钮,在ejs中,加增加账单按钮
a href="/account/create"
操作数据库,实现文档删除,项目文件夹\routes\index.js路由规则中
var express = require('express')
var router = express.Router()
//导入 lowdb
const low = require('lowdb')
const FileSync = require('lowdb/adapters/FileSync')
const adapter = new FileSync(__dirname + '/../data/db.json')
//获取 db 对象
const db = low(adapter)
//导入 shortid
const shortid = require('shortid')
//导入 moment
const moment = require('moment')
const AccountModel = require('../models/AccountModel')
//测试
// console.log(moment('2023-02-24').toDate())
//格式化日期对象
// console.log(moment(new Date()).format('YYYY-MM-DD'));
//记账本的列表
router.get('/account', function (req, res, next) {
//获取所有的账单信息
// let accounts = db.get('accounts').value();
//读取集合信息
AccountModel.find().sort({ time: -1 }).exec((err, data) => {
if (err) {
res.status(500).send('读取失败~~~')
return
}
// 先确认响应成功的data
console.log(data)
//响应成功的提示,交给ejs渲染
res.render('list', { accounts: data, moment: moment })
})
})
//添加记录
router.get('/account/create', function (req, res, next) {
res.render('create')
})
//新增记录
router.post('/account', (req, res) => {
// 查看表单数据
// 通过打印,确认表单提交的内容(主要是数据属性),与上节定义的模型/表的字段是否对得上
console.log(req.body)
// 在插入数据库之前也可以先修改时间
// req.body.time = moment(req.body.time).toDate()
//插入数据库
AccountModel.create({
// es6语法,展开req.body所有属性,放入对象中
...req.body,
// 修改其中的 time 属性的值
// 字符串转moment对象,再.toDate()转日期对象
time: moment(req.body.time).toDate()
}, (err, data) => {
if (err) {
// 如果有问题,返回错误的500响应
res.status(500).send('插入失败~~')
return
}
//成功提醒
res.render('success', { msg: '添加成功哦~~~', url: '/account' })
})
})
//删除记录
router.get('/account/:id', (req, res) => {
//获取 params 的 id 参数
let id = req.params.id
//删除
AccountModel.deleteOne({ _id: id }, (err, data) => {
if (err) {
res.status(500).send('删除失败~')
return
}
//提醒
res.render('success', { msg: '删除成功~~~', url: '/account' })
})
})
module.exports = router
- 二次确认+添加按钮,项目文件夹\views\list.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css" rel="stylesheet" />
<style>
label {
font-weight: normal;
}
.panel-body .glyphicon-remove {
display: none;
}
.panel-body:hover .glyphicon-remove {
display: inline-block
}
</style>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-xs-12 col-lg-8 col-lg-offset-2">
<div class="row">
<h2 class="col-xs-6">记账本</h2>
<h2 class="col-xs-6 text-right"><a href="/account/create" class="btn btn-primary">添加账单</a></h2>
</div>
<hr />
<div class="accounts">
<% accounts.forEach(item=> { %>
<div class="panel <%= item.type=== -1 ? 'panel-danger' : 'panel-success' %>">
<div class="panel-heading">
<%= moment(item.time).format('YYYY-MM-DD') %>
</div>
<div class="panel-body">
<div class="col-xs-6">
<%= item.title %>
</div>
<div class="col-xs-2 text-center">
<span class="label <%= item.type=== -1 ? 'label-warning' : 'label-success' %>">
<%= item.type===-1 ? '支出' : '收入' %>
</span>
</div>
<div class="col-xs-2 text-right">
<%= item.account %> 元
</div>
<div class="col-xs-2 text-right">
<a class="delBtn" href="/account/<%= item._id %>">
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
</a>
</div>
</div>
</div>
<% }) %>
</div>
</div>
</div>
</div>
</body>
<script>
//获取所有的 delBtn
let delBtns = document.querySelectorAll('.delBtn');
//绑定事件
delBtns.forEach(item => {
item.addEventListener('click', function (e) {
if (confirm('您确定要删除该文档么??')) {
return true;
} else {
//阻止默认行为
e.preventDefault();
}
});
})
</script>
</html>