轻笑Chuckle
外观

学习笔记

NodeJS-MongoDB

MongoDB使用,mongoose连接、增删改查、模块化,图形化软件

4 分钟 1,618 字 次浏览 条评论

简介

MongoDB 基于分布式文件存储的数据库

相较于用 db.json 纯文件管理数据,用数据库管理数据速度更快、扩展性高、安全性高

MongoDB的操作语法与JS相似

核心概念:

  1. 数据库(database) 数据库是一个数据仓库,数据库服务下可以创建很多数据库,数据库中可以存 放很多集合
  2. 集合(collection) 集合类似于 JS 中的数组,在集合中可以存放很多文档,一个集合存放一种文档(字段类型相同的文档)
  3. 文档(document) 文档是数据库中的最小单位,类似于 JS 中的对象,一个文档中有许多字段(对象的属性)

命令行交互

在将bin文件夹添加至环境变量后,在终端中输入 mongod 启动服务端,mongo 启动客户端

1、数据库命令

// 显示所有的数据库
show dbs
// 切换到指定数据库,若不存在则创建
use <数据库名>
// 显示当前所在的数据库
db
// 删除当前数据库
use <数据库名>
db.dropDatabase()

2、集合命令

// 创建集合
db.createCollection('集合名')
// 显示当前数据库中的所有集合
show collections
// 删除某个集合
db.集合名.drop()
// 重命名集合
db.集合名.renameCollection('新名')

3、文档命令

// 插入文档
db.集合名.insert(文档对象)
// 查询文档,_id 是 mongodb 自动生成的唯一编号,用来唯一标识文档
db.集合名.find(查询条件)
// 更新文档
db.集合名.update(查询条件,新的文档)
// 删除文档
db.集合名.remove(查询条件)

Mongoose

Mongoose 是一个对象文档模型库,在异步环境中工作,用代码操作 mongodb 数据库

连接数据库:

const mongoose = require('mongoose');
// 连接数据库 mongodb://127.0.0.1:<端口号,默认27017可不写>/<数据库名,没有会自动创建>
mongoose.connect('mongodb://127.0.0.1/test');
// 设置回调
// 连接成功回调,使用once回调函数只执行一次
mongoose.connection.once('open', () => {
console.log('连接成功');
});
// 连接错误回调
mongoose.connection.on('error', () => {
console.log('连接失败');
});
// 连接关闭回调
mongoose.connection.on('close', () => {
console.log('连接关闭');
});

常用方法: 1、创建文档模型对象:

  1. mongoose.Schema() 创建文档的结构对象,约束文档字段的数据类型
  2. mongoose.model(<集合名称>, <结构对象>) 创建模型对象,对文档操作的封装对象,集合有则使用无则自动创建,集合名称会自动变为复数

2、插入、增加文档:

  1. create()) 创建并插入一个新文档
  2. insertMany() 传入文档数组批量添加新文档

3、删除文档:

  1. deleteOne() 删除一条文档
  2. deleteMany() 批量删除

4、更新文档:

  1. updateOne() 更新一条文档
  2. updateMany() 批量更新

5、查找、读取文档:

  1. findOne() 查找一条
  2. findById() 通过_id找一条
  3. find() 批量查找,无论找到多少条都返回数组

6、条件控制:

  1. > $gt
  2. < $lt
  3. >= $gte
  4. <= $lte
  5. !== $ne
  6. || $or 也可以用 ||
  7. && $and 也可以用 &&
  8. 正则匹配 {author: /鱼/}

7、规则控制:

  1. select() 字段筛选
  2. sort() 排序
  3. skip() 数据截取,跳过
  4. limit() 数据截取,限定

插入文档

**插入新文档流程:**在open事件的回调函数中操作

  1. mongoose.Schema() 创建文档的结构对象,约束文档字段的数据类型
  2. mongoose.model(<集合名称>, <结构对象>) 创建模型对象,对文档操作的封装对象,集合有则使用无则自动创建,集合名称会自动变为复数
  3. 模型对象.create({文档数据对象}).then(data=>{}) 创建新文档
// 在open事件的回调函数中操作
mongoose.connection.once('open', () => {
console.log('连接成功');
// 创建文档的结构对象BookSchema
// 并约束文档字段的数据类型
let BookSchema = mongoose.Schema({
name: String,
author: String,
price: Number
});
// 创建模型对象,对文档操作的封装对象,第一个参数是集合,会自动创建
let BookModel = mongoose.model('books', BookSchema);
// 使用模型对象去增删改查
// 创建新文档
BookModel.create({
name: '三体',
author: '刘慈欣',
price: 30
}).then(data=>{
console.log(data);
// {
// name: '三体',
// author: '刘慈欣',
// price: 30,
// _id: new ObjectId("6442809af5fae6b874c172f0"),
// __v: 0
// }
}).catch(err=>{
console.log(err);
})
// 关闭数据库连接(项目运行过程中不会添加该代码)
// mongoose.disconnect();
});

批量插入文档

create() 一次只能新增一个文档,使用 insertMany() 传入文档数组批量添加新文档

mongoose.connection.once('open', () => {
// 文档结构对象
let BookSchema = new mongoose.Schema({
name: String,
author: String,
price: Number,
is_hot: Boolean
});
// 文档模型对象
let BookModel = mongoose.model('novels', BookSchema);
// 批量添加文档
BookModel.insertMany([{
name: '西游记',
author: '吴承恩',
price: 19.9,
is_hot: true
}, {
name: '红楼梦',
author: '曹雪芹',
price: 29.9,
is_hot: true
}, {
name: '三国演义',
author: '罗贯中',
price: 25.9,
is_hot: true
}, {
name: '水浒传',
author: '施耐庵',
price: 20.9,
is_hot: true
}, {
name: '活着',
author: '余华',
price: 19.9,
is_hot: true
}, {
name: '狂飙',
author: '徐纪周',
price: 68,
is_hot: true
}, {
name: '大魏能臣',
author: '黑男爵',
price: 9.9,
is_hot: false
},
{
name: '知北游',
author: '洛水',
price: 59,
is_hot: false
},{
name: '道君',
author: '跃千愁',
price: 59,
is_hot: false
},{
name: '七煞碑',
author: '游泳的猫',
price: 29,
is_hot: false
},{
name: '独游',
author: '酒精过敏',
price: 15,
is_hot: false
},{
name: '大泼猴',
author: '甲鱼不是龟',
price: 26,
is_hot: false
},
{
name: '黑暗王者',
author: '古羲',
price: 39,
is_hot: false
},
{
name: '不二大道',
author: '文刀手予',
price: 89,
is_hot: false
},
{
name: '大泼猴',
author: '甲鱼不是龟',
price: 59,
is_hot: false
},
{
name: '长安的荔枝',
author: '马伯庸',
price: 45,
is_hot: true
},
{
name: '命运',
author: '蔡崇达',
price: 59.8,
is_hot: true
},
{
name: '如雪如山',
author: '张天翼',
price: 58,
is_hot: true
},
{
name: '三体',
author: '刘慈欣',
price: 23,
is_hot: true
},
{
name: '秋园',
author: '杨本芬',
price: 38,
is_hot: true
},
{
name: '百年孤独',
author: '范晔',
price: 39.5,
is_hot: true
},
{
name: '在细雨中呼喊',
author: '余华',
price: 25,
is_hot: true
}]);
});

字段

文档结构可选的常用字段类型:

  1. String 字符串
  2. Number 数字
  3. Boolean 布尔值
  4. Array 数组,也可以使用 [] 来标识
  5. Date 日期
  6. Buffer Buffer 对象
  7. Mixed 任意类型,需要使用 mongoose.Schema.Types.Mixed 指定
  8. ObjectId 文档对象 ID,用于设置外键,保存其它文档的id,需要使用 mongoose.Schema.Types.ObjectId 指定
  9. Decimal128 高精度数字,需要使用 mongoose.Schema.Types.Decimal128 指定

**字段值验证:**一些内建验证器,可以对字段值进行验证,或者说进一步约束字段内容

  1. required: true 设置必填项
  2. default: 'qx' 设置默认值
  3. enum: ['男','女'] 枚举值,字段值必须在数组中
  4. unique: true 设置值唯一,需要重建集合才有效果

在创建文档的结构对象时添加字段验证

let BookSchema = mongoose.Schema({
name: {
type: String,
required: true, // 必填项
unique: true, // 唯一值
},
author: {
type: String,
default: '匿名' // 默认值
},
style: {
type: String,
required: true, // 必填项
enum: ['科幻','言情','玄幻'] // 枚举值
},
price: {
type: Number,
required: true // 必填项
},
});

删除文档

deleteOne() 删除一条文档,deleteMany() 批量删除

// 删除单条文档
BookModel.deleteOne({ _id: '64428b3a01df4070e7bc2df3' })
.then(data => {
console.log(data);// { acknowledged: true, deletedCount: 1 }
})
.catch(err => {
console.log('删除失败');
})
// 批量删除文档
BookModel.deleteMany({ is_hot: false })
.then(data => {
console.log(data);// { acknowledged: true, deletedCount: 9 }
})
.catch(err => {
console.log('删除失败');
})

更新文档

updateOne() 和 updateMany()

// 更新单条
BookModel.updateOne({name: '红楼梦'}, {price: 9.9})
.then(data=>{
console.log(data);
})
//更新多条
BookModel.updateMany({is_hot: true}, {is_hot: false})
.then(data=>{
console.log(data);
// {
// acknowledged: true,
// modifiedCount: 12,
// upsertedId: null,
// upsertedCount: 0,
// matchedCount: 12
// }
})

查找文档

findOne() 查找一条,findById() 通过_id找一条,find() 批量查找,无论找到多少条都返回数组

// 读取单条
BookModel.findOne({name: '狂飙'})
.then(data=>{
console.log(data);
// {
// _id: new ObjectId("6442a4a0f5bcc6fb8cc84f9a"),
// name: '狂飙',
// author: '徐纪周',
// price: 68,
// is_hot: true,
// __v: 0
// }
})
// 通过_id获取
BookModel.findById('6442a4a0f5bcc6fb8cc84fa7')
.then(data=>{
console.log(data);
// {
// _id: new ObjectId("6442a4a0f5bcc6fb8cc84fa7"),
// name: '三体',
// author: '刘慈欣',
// price: 23,
// is_hot: true,
// __v: 0
// }
})
//读取多条,没用参数则获取全部文档
BookModel.find({author: '余华'})
.then(data=>{
console.log(data);
// [
// {
// _id: new ObjectId("6442a4a0f5bcc6fb8cc84f99"),
// name: '活着',
// author: '余华',
// price: 19.9,
// is_hot: true,
// __v: 0
// },
// {
// _id: new ObjectId("6442a4a0f5bcc6fb8cc84faa"),
// name: '在细雨中呼喊',
// author: '余华',
// price: 25,
// is_hot: true,
// __v: 0
// }
// ]
})

条件控制

mongodb中没有条件运算符,必须使用替代符号

  1. > $gt
  2. < $lt
  3. >= $gte
  4. <= $lte
  5. !== $ne
  6. || $or 也可以用 ||
  7. && $and 也可以用 &&
// 价格高于20
BookModel.find({price: {$gte:20}})
// 价格高于20且低于30
BookModel.find({ $and: [{ price: { $gte: 20 }, price: { $lt: 30 } }] })
BookModel.find({ price: { $gte: 20 } && { $lt: 30 } })
// 查找甲鱼不是龟写的价格低于30的书
BookModel.find({ $and: [{ author: '甲鱼不是龟', price: { $lt: 30 } }] })

**正则匹配:**条件中可以直接使用 JS 的正则语法,通过正则可以进行模糊查询

// 搜索作者名带鱼写的书
BookModel.find({ author: /鱼/})

规则控制

1、字段筛选 select() 查找时只查找文档的某些字段,提高查找效率,1 需要,0 排除,_id默认为1

BookModel.find({price: { $lt: 30 }}).select({name:1,author:1,price:1})
.then(data=>{
console.log(data);
})

2、排序 sort(),按某个字段的升序(1)或降序(-1)排序

BookModel.find().sort({price:1}).select({name:1,author:1,price:1})
.then(data=>{
console.log(data);
})

3、数据截取,skip() 跳过,limit() 限定

BookModel.find().sort({price:-1}).select({name:1,price:1}).limit(3)
.then(data=>{
console.log(data);
})
BookModel.find().sort({price:-1}).select({name:1,price:1}).skip(3)
.then(data=>{
console.log(data);
})

代码模块化

mongoose的模块化:

  1. 将数据库连接部分独立,并放入一个函数中暴露出去
  2. 将数据库中不同的文档对象模型每个单独一个js文件独立到modules文件夹中,将文档对象模型暴露出去
  3. 将数据库的连接信息独立到config.js再暴露出去给db.js使用
  4. index.js导入数据库连接函数和文档对象模型,并将连接数据库成功后的操作,都放在数据库连接函数的第一个参数中,作为连接成功的回调函数
const mongoose = require('mongoose');
mongoose.set('strictQuery', true);
mongoose.connect('mongodb://127.0.0.1:27017/test');
mongoose.connection.once('open', () => {
// 文档结构对象
let BookSchema = new mongoose.Schema({
name: String,
author: String,
price: Number,
is_hot: Boolean
});
// 文档模型对象
let BookModel = mongoose.model('novels', BookSchema);
// 读取单条
BookModel.findOne({name: '狂飙'})
.then(data=>{
console.log(data);
})
// 通过_id获取
});
// 设置连接错误的回调
mongoose.connection.on('error', () => {
console.log('连接失败');
});
//设置连接关闭的回调
mongoose.connection.on('close', () => {
console.log('连接关闭');
});

模块化后:

作用:当需要对数据库中一个集合操作时,只需要在models文件夹中新建该集合的文档对象模型,然后在index.js中导入,当要连接其它mongodb数据库时也只需要修改config.js

const mongoose = require('mongoose');
// 将数据库连接函数导入
const db = require('./db/db');
// 导入BookModel文档对象模型
const BookModel = require('./models/BookModel');
// 传入两个回调函数
db(() => {
// 连接成功则对数据库进行操作,增删改查
// 查找《狂飙》的信息
BookModel.findOne({ name: '狂飙' })
.then(data => {
console.log(data);
})
});
const config = {
HOST: '127.0.0.1',
PORT: 27017,
NAME: 'test'
}
module.exports = config;
// 暴露一个函数,success数据库连接成功的回调,error连接失败的回调
module.exports = function (success, error = () => { console.log('连接失败'); }) {
const mongoose = require('mongoose');
// 导入数据库连接配置文件
const {HOST,PORT,NAME} = require('../config');
mongoose.connect(`mongodb://${HOST}:${PORT}/${NAME}`);
mongoose.connection.once('open', () => {
success();
});
// 设置连接错误的回调
mongoose.connection.on('error', () => {
error();
});
//设置连接关闭的回调
mongoose.connection.on('close', () => {
console.log('连接关闭');
});
}
const mongoose = require('mongoose');
// 文档结构对象
let BookSchema = new mongoose.Schema({
name: String,
author: String,
price: Number,
is_hot: Boolean
});
// 文档模型对象
let BookModel = mongoose.model('novels', BookSchema);
// 将文档模型对象暴露出去
module.exports = BookModel;

图形化管理

使用图形化软件更方便操控数据库 Robo3T Navicat

记账本优化

使用 mongodb 数据库替代 lowdb 文件管理,使用 moment 处理日期类型

主要文件: 数据库相关:

// 暴露一个函数,success数据库连接成功的回调,error连接失败的回调
module.exports = function (success, error = () => { console.log('连接失败'); }) {
const mongoose = require('mongoose');
// 导入数据库连接配置文件
const { HOST, PORT, NAME } = require('../config');
mongoose.connect(`mongodb://${HOST}:${PORT}/${NAME}`);
mongoose.connection.once('open', () => {
success();
});
// 设置连接错误的回调
mongoose.connection.on('error', () => {
error();
});
//设置连接关闭的回调
mongoose.connection.on('close', () => {
console.log('连接关闭');
});
}
const mongoose = require('mongoose');
// 文档结构对象
let AccountSchema = new mongoose.Schema({
matter: {
type: String,
required: true
},
date: {
type: Date,
required: true
},
type: {
type: String,
enum: ['支出', '收入'],
default: '支出'
},
account: {
type: Number,
required: true
},
remark: {
type: String,
default: '无'
}
});
// 文档模型对象
let AccountModel = mongoose.model('accounts', AccountSchema);
// 将文档模型对象暴露出去
module.exports = AccountModel;
const config = {
HOST: '127.0.0.1',
PORT: 27017,
NAME: 'test'
}
module.exports = config;

**启动http服务的www文件:**导入数据库连接函数,将http服务的代码都扔进db的回调函数中,待数据库连接成功再执行回调函数运行http服务

#!/usr/bin/env node
// 导入数据库连接函数
const db = require('../db/db');
// 将http相关操作放到连接成功的回调中
db(()=>{
/**
* Module dependencies.
*/
var app = require('../app');
var debug = require('debug')('keepingbook: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);
}
})

**路由相关:**index.js内有增删该查的页面、接口路由,在这个文件中引入文档对象模型,就能在对应路由内对数据库文档做对应操作

var express = require('express');
var router = express.Router();
// 导入moment处理日期
const moment = require('moment');
// 导入文档模型对象
const AccountModel = require('../models/AccountModel');
// 记账本页面路由
router.get('/', function (req, res, next) {
// 读取所有数据,按日期降序
AccountModel.find().sort({ date: -1 })
.then(data => {
// 将数据数组传递过去遍历渲染,为了格式化日期,将moment传入
res.render('index', { content: data, moment });
})
.catch(err => {
console.log(err);
// 查找失败则返回500
res.status(500).render('tip', { msg: '查找失败!', url: '/' });
})
});
// 添加记录页面路由
router.get('/add', function (req, res, next) {
res.render('add');
});
// 添加记录post接口路由
router.post('/add', function (req, res, next) {
// 插入一条数据
AccountModel.create({
// 解构赋值
...req.body,
// 覆盖修改日期为日期对象
date: moment(req.body.date).toDate()
})
.then(data => {
console.log(data);
// 成功跳转到提示页
res.render('tip', { msg: '添加成功!', url: '/' });
})
.catch(err => {
console.log(err);
// 添加失败则返回500
res.status(500).render('tip', { msg: '添加失败!', url: '/' });
});
});
// 根据id删除数据,使用路由参数
router.get('/delete/:id', (req, res) => {
// 获取路由参数id
let id = req.params.id;
AccountModel.deleteOne({ _id: id })
.then(data => {
console.log(data);
// 删除成功跳转提示页
res.render('tip', { msg: '删除成功!', url: '/' });
})
.catch(err => {
console.log(err);
res.status(500).render('tip', { msg: '删除失败!', url: '/' });
})
});
module.exports = router;

除了index.ejs,其它页面无变化

<div class="date"><%= moment(item.date).format('YYYY-MM-DD') %></div>

评论

快速前往