简介
MongoDB 基于分布式文件存储的数据库
相较于用 db.json 纯文件管理数据,用数据库管理数据速度更快、扩展性高、安全性高
MongoDB的操作语法与JS相似
核心概念:
- 数据库(database) 数据库是一个数据仓库,数据库服务下可以创建很多数据库,数据库中可以存 放很多集合
- 集合(collection) 集合类似于 JS 中的数组,在集合中可以存放很多文档,一个集合存放一种文档(字段类型相同的文档)
- 文档(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、创建文档模型对象:
mongoose.Schema()创建文档的结构对象,约束文档字段的数据类型mongoose.model(<集合名称>, <结构对象>)创建模型对象,对文档操作的封装对象,集合有则使用无则自动创建,集合名称会自动变为复数
2、插入、增加文档:
create())创建并插入一个新文档insertMany()传入文档数组批量添加新文档
3、删除文档:
deleteOne()删除一条文档deleteMany()批量删除
4、更新文档:
updateOne()更新一条文档updateMany()批量更新
5、查找、读取文档:
findOne()查找一条findById()通过_id找一条find()批量查找,无论找到多少条都返回数组
6、条件控制:
- >
$gt - <
$lt - >=
$gte - <=
$lte - !==
$ne - ||
$or也可以用 || - &&
$and也可以用 && - 正则匹配
{author: /鱼/}
7、规则控制:
select()字段筛选sort()排序skip()数据截取,跳过limit()数据截取,限定
插入文档
**插入新文档流程:**在open事件的回调函数中操作
mongoose.Schema()创建文档的结构对象,约束文档字段的数据类型mongoose.model(<集合名称>, <结构对象>)创建模型对象,对文档操作的封装对象,集合有则使用无则自动创建,集合名称会自动变为复数模型对象.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 }]);
});字段
文档结构可选的常用字段类型:
- String 字符串
- Number 数字
- Boolean 布尔值
- Array 数组,也可以使用 [] 来标识
- Date 日期
- Buffer Buffer 对象
- Mixed 任意类型,需要使用 mongoose.Schema.Types.Mixed 指定
- ObjectId 文档对象 ID,用于设置外键,保存其它文档的id,需要使用 mongoose.Schema.Types.ObjectId 指定
- Decimal128 高精度数字,需要使用 mongoose.Schema.Types.Decimal128 指定
**字段值验证:**一些内建验证器,可以对字段值进行验证,或者说进一步约束字段内容
- required: true 设置必填项
- default: 'qx' 设置默认值
- enum: ['男','女'] 枚举值,字段值必须在数组中
- 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中没有条件运算符,必须使用替代符号
- >
$gt - <
$lt - >=
$gte - <=
$lte - !==
$ne - ||
$or也可以用 || - &&
$and也可以用 &&
// 价格高于20BookModel.find({price: {$gte:20}})// 价格高于20且低于30BookModel.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的模块化:
- 将数据库连接部分独立,并放入一个函数中暴露出去
- 将数据库中不同的文档对象模型每个单独一个js文件独立到modules文件夹中,将文档对象模型暴露出去
- 将数据库的连接信息独立到config.js再暴露出去给db.js使用
- 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>
评论