主题
使用 Node 操作数据库 - Mongoose与附录(Day12 3/3)
本页对应 Day12_Node&MongoDB,共 7 段视频,约 1 小时 12 分。
学习目标
- 能够使用 Mongoose 连接 MongoDB,并监听连接成功与失败事件,说明连接代码应放在应用启动流程中的位置。
- 能够为集合定义 Schema、创建并导出 Model,并在其他模块中正确导入和使用该 Model。
- 能够使用 Mongoose Model 完成单条/批量新增、单条/多条删除、单条/多条更新,并验证数据库结果。
- 能够使用 Mongoose 完成单条查询、多条查询、全量查询,并应用字段筛选、排序、skip 与 limit。
- 能够在记账本项目中集成 MongoDB,在路由回调中使用 Model 实现账单新增、列表查询和按
_id删除,并渲染成功或失败页面。
知识点
单条与批量新增:先明确连接生命周期
为什么需要:Mongoose 是 Node 操作 MongoDB 的常用 ODM,新增数据是 CRUD 的入口。转写稿先演示 insertOne,再演示 insertMany,是为了区分“一个文档”和“多个文档”的参数形式。
怎么想:单条新增第一个参数是对象;批量新增第一个参数是数组,数组里放多个对象。实际开发中数据库连接要长期保持,不能每次操作完就关闭;演示中调用 mongoose.connection.close() 只是验证脚本能退出。
怎么做:
js
const mongoose = require('mongoose');
mongoose.connect('mongodb://127.0.0.1:27017/project04');
const songSchema = new mongoose.Schema({
author: String,
language: String,
duration: Number,
hot: Number,
title: String
});
const Song = mongoose.model('Song', songSchema, 'song');
// 单条
Song.insertOne({ title: '示例', author: '某某', hot: 100 });
// 批量:require JSON 会自动转成对象
Song.insertMany(require('./data.json').song_list);易错点:require('./data.json') 得到的是对象,要取对象里的数组属性;Schema 只声明 5 个字段时,源数据多余字段不会插入;不要传空数组冒充批量数据。转写稿中运行 02 报“user model 未定义”,原因就是复制代码后模型名没改,改回歌曲模型即可。
删除:deleteOne 与 deleteMany 的区别
为什么需要:删除要能精确删一条,也能按条件删多条。转写稿先试 delete 报错,才改为 deleteOne,这是方法名记忆点。
怎么想:deleteOne(条件, 回调) 只删除满足条件的第一个文档;deleteMany(条件, 回调) 删除所有满足条件的文档;deleteMany({}) 会清空集合。
怎么做:
js
Song.deleteOne({ author: '林菲如' }).then(res => {
console.log(res.deletedCount); // 删除数量
});
Song.deleteMany({ author: '林菲如' });
// 清空集合
Song.deleteMany({});易错点:返回的是删除结果,不是被删文档;deleteOne 不是删所有;清空后可用 02 批量新增脚本恢复数据,说明“程序化造数据”比手工恢复可靠。
更新:updateOne 与 updateMany 的局部更新
为什么需要:业务里经常只改某个字段,而不是整份文档替换。转写稿用作者改名演示局部更新。
怎么想:updateOne 改满足条件的第一条,updateMany 改所有满足条件的文档。Mongoose 有 Schema 约束,通常只更新指定字段,不会随意把整条文档覆盖成缺字段状态。
怎么做:
js
Song.updateOne(
{ author: '蔡徐坤' },
{ $set: { author: '高孝乐' } }
).then(res => console.log(res));
Song.updateMany(
{ author: '蔡徐坤' },
{ $set: { author: '林菲如' } }
);易错点:条件不写会命中第一条;要批量改必须用 updateMany;转写稿里“高考证/高考乐/高小乐”是同音转写误差,代码里应保持同一个目标值,避免条件与更新值写混。
查询:findOne、findById、find 与链式操作
为什么需要:查询是最常用操作,还要支持按条件、按 ID、字段筛选、排序和截取。
怎么想:查一条用 findOne 或 findById;查多条用 find;find() 不写条件就是全查。字段筛选用 select,排序用 sort,截取用 skip 和 limit,最后用 exec 执行回调。
怎么做:
js
Song.findOne({ author: '高孝乐' }).then(data => console.log(data));
Song.findById('文档_id字符串').then(data => console.log(data));
Song.find({ hot: { $gt: 300 } })
.select({ author: 1, title: 1, _id: 0 })
.sort({ hot: 1 }) // 1 升序,-1 降序
.skip(90)
.limit(10)
.exec((err, data) => console.log(data));易错点:_id 默认会带出,不想显示要显式设 _id: 0;skip 从 0 开始,转写稿中“从第 90 个开始看 10 个”出现条数差一,就是索引从 0 起;链式查询别忘了 exec。
记账本项目接入:连接一次,模型独立,路由做 CRUD
为什么需要:项目启动后要长期服务,不能每个请求都连数据库。转写稿的分析是:连接放在启动文件 www,连接成功后再启动 HTTP 服务;模型放到 models,谁需要谁导入。
怎么想:整个项目连同一个数据库一次即可;一个集合对应一个 Schema 和一个 Model;增删改查写在路由回调里。
怎么做:
js
// www:连接成功后再启动服务
mongoose.connect('mongodb://127.0.0.1:27017/account_project');
mongoose.connection.on('error', err => console.log('数据库连接失败', err));
mongoose.connection.once('open', () => {
server.listen(3000);
});
// models/account.js
const mongoose = require('mongoose');
const accountSchema = new mongoose.Schema({
title: String,
time: String,
type: Number,
account: Number
});
module.exports = mongoose.model('Account', accountSchema, 'accounts');
// routes/account.js
const Account = require('../models/account');
Account.create(req.body, err => { /* 渲染成功/失败 */ });
Account.find((err, data) => { /* 渲染列表 */ });
Account.deleteOne({ _id: req.query.id }, err => { /* 删除 */ });易错点:每个模块都要单独 require('mongoose');删除时模板里要传 _id,不是 id;不要在每个路由请求里 connect;转写稿还提到“假删”思路——用 isDelete 标记而不真删,查询时只查未删除数据。
本节附录把 mongoose 的增删改查与记账本项目升级串起来。下面按“为什么需要→怎么想→怎么做→易错点”整理,代码按转写稿示意整理。
连接与关闭:理解 mongoose 生命周期
为什么需要:执行完脚本后进程不退出,容易误以为程序卡住。怎么想:mongoose.connection.close() 能关闭连接;但实际后端服务要常驻,不能每次请求都连一次、断一次。怎么做:学习脚本可在确认写入成功后关闭;项目服务只连一次。易错点:生产服务不要随手关连接;要监听 error,连接失败时不要继续启动 HTTP 服务。
js
const mongoose = require('mongoose');
mongoose.connect('mongodb://127.0.0.1:27017/project04');
// 学习脚本可关闭:mongoose.connection.close();批量插入:数组与 JSON 导入
为什么需要:一条条 create 效率低。怎么想:insertMany 接收对象数组;Schema 只保留声明字段,多余字段不会入库。怎么做:用 require 导入 JSON,JSON 会自动转成对象,取出数组交给 insertMany。易错点:运行 node 02 报 userModel is not defined,是模型名忘改;空数组不是“全部”,不要误写;JSON 不需要再手动 JSON.parse。
js
const SongSchema = new mongoose.Schema({
title: String, author: String, language: String,
duration: Number, hot: Number
});
const SongModel = mongoose.model('songs', SongSchema);
SongModel.insertMany(list, (err, data) => { /* ... */ });删除:deleteOne 与 deleteMany
为什么需要:按条件删一条或删一批。怎么想:deleteOne 只删第一个匹配文档,deleteMany 删所有匹配;空对象 {} 会清空集合。怎么做:条件写成对象,回调里看 err,结果里有 deletedCount。易错点:没有 delete 方法,报错后应改 deleteOne/deleteMany;清空后可用插入脚本恢复数据。
js
SongModel.deleteOne({ author: '某作者' }, (err, res) => {});
SongModel.deleteMany({}, (err, res) => {});更新:updateOne/updateMany 不整体覆盖
为什么需要:只改部分字段,避免用新文档覆盖旧文档。怎么想:Schema 已约束字段,更新只改给定字段;updateOne 改第一个匹配,updateMany 改所有匹配。怎么做:条件对象 + 更新对象。易错点:条件写成空对象会全改;不要以为会整体替换旧文档。
js
SongModel.updateOne({ author: '某作者' }, { author: '新作者' }, (err, res) => {});
SongModel.updateMany({ author: '某作者' }, { author: '新作者' }, (err, res) => {});查询:findOne、findById、find 与链式操作
为什么需要:单条、按 ID、多条、字段筛选、排序分页都常用。怎么想:findOne 返回对象,find 返回数组;select 指定字段,_id 默认带出,设 _id:0 可排除;sort 的 1 升序、-1 降序;skip 从 0 开始。怎么做:find(...).select(...).sort(...).skip(...).limit(...).exec(cb)。易错点:skip(90).limit(10) 实际是第 91—100 条;字段名拼错会查不到;回调参数顺序是 err, data。
js
SongModel.find({ hot: { $gt: 300 } })
.select({ author: 1, title: 1, _id: 0 })
.sort({ hot: 1 })
.skip(90).limit(10)
.exec((err, data) => {});项目升级:先连库,再启动 HTTP
为什么需要:数据库连不上时启动服务没有意义。怎么想:整个项目只连一次,所有路由共享连接;连接成功后回调里再 app.listen。怎么做:在 www 中导入 mongoose、建立连接、监听 open 和 error,把启动 HTTP 的代码放进成功回调。易错点:不要在路由回调里连接数据库,否则每次请求都新建连接;连接失败要提示并停止启动。
js
mongoose.connect('mongodb://127.0.0.1:27017/account-project');
mongoose.connection.on('error', err => { /* 提示失败 */ });
mongoose.connection.on('open', () => { app.listen(3000); });Schema 与 Model:models 目录暴露模型
为什么需要:一个集合对应一个 Schema/Model,路由需要模型做 CRUD。怎么想:models/account.js 专门创建 Schema 和 Model,并 module.exports 暴露 Model;谁需要谁 require。怎么做:每个模块都要自己导入 mongoose。易错点:集合名与模型名要对应;不要把连接代码写进模型文件。
js
const mongoose = require('mongoose');
const AccountSchema = new mongoose.Schema({
title: String, time: String, type: Number,
account: Number, remark: String
});
module.exports = mongoose.model('accounts', AccountSchema);路由 CRUD:create、find、deleteOne
为什么需要:把数据库操作放进路由回调,页面才能读写数据。怎么想:添加用 create,首页用 find,删除用 deleteOne({_id: req.query.id})。怎么做:模板里写 item._id,不是 item.id。易错点:删除/添加失败要渲染失败页;真实项目常做“假删”,用 isDelete 标记而不真删,便于恢复数据。
js
AccountModel.create(req.query, err => { /* 成功或失败页面 */ });
AccountModel.find((err, data) => { res.render('index', { data }); });
AccountModel.deleteOne({ _id: req.query.id }, err => { /* ... */ });第一步:连接成功后再启动 HTTP 服务
为什么需要:记账本项目要把账单存进 MongoDB,就必须先建立数据库连接。转写稿里反复强调:连接数据库的代码不能写在路由回调里,否则用户每请求一次就可能创建一个连接,容易把连接“连坏”。连接应该在项目启动时做一次。
怎么想:项目启动入口是 bin/www,HTTP 服务也是在这里启动的。因此应先导入 mongoose、建立连接、监听连接成功和失败事件;只有连接成功,才启动后面的 HTTP 服务。数据库都连不上,启动服务没有意义。
怎么做:在 bin/www 中完成前三步:
js
const mongoose = require('mongoose');
mongoose.connect('mongodb://127.0.0.1:27017/account-project');
mongoose.connection.on('error', err => {
console.log('数据库连接失败,应用无法启动,请排查错误', err);
});
mongoose.connection.once('open', () => {
// 数据库连接成功后,再启动 HTTP 服务
});原笔记步骤要保留:
第一步:
当数据库连接成功之后,才能启动 http 服务
在 bin/www 里面:
1. 导入模块
2. 建立连接
3. 监听连接成功事件 连接失败事件易错点:不要把连接代码写进 routes/account.js;不要连接失败还继续启动服务;调试脚本里可以关闭连接,但实际后端服务运行期间不需要关闭连接。
第二步:每个集合一个模型文件
为什么需要:Mongoose 需要 schema 规定文档结构,再用 model 对应集合。记账项目后面可能既有账单集合,又有用户集合,所以每个集合应单独建模型文件,谁需要谁导入。
怎么想:models/account.js 只负责创建账单的 schema 和 model,并把模型对象作为暴露数据。转写稿里说得很清楚:需要几个集合,就建几个模型文件。
怎么做:创建 models/account.js,保留原笔记步骤:
第二步
创建 models/account.js 文件
4. 创建 schema
5. 创建 model
并将Model对象作为暴露数据关键代码思路:
js
const mongoose = require('mongoose');
const accountSchema = new mongoose.Schema({
title: String,
time: String,
type: Number,
account: Number
});
module.exports = mongoose.model('accounts', accountSchema);易错点:模型文件里也要各自导入 mongoose;暴露的是 model 对象,不是 schema;集合名和模型名要对应好。转写稿调试时出现“user model 没有定义”,就是因为变量名忘了改。
第三步:在路由里导入模型做增删改查
为什么需要:路由负责接收请求并返回页面或数据,具体数据库操作应交给模型完成。这样连接、模型、路由职责分开。
怎么想:在 routes/account.js 导入 models/account.js 得到的 model 对象,然后调用 create、deleteOne、deleteMany、updateOne、updateMany、find、findOne、findById 等方法。原笔记步骤保留:
第三步
在 rountes/account.js 中
导入 models/account.js 得到 model 对象
使用 model 对象进行增删改查怎么做:添加用 create(req.body, ...);列表用 find((err, data) => ...);删除用 deleteOne({_id: req.query.id}, ...);更新用 updateOne(条件, 更新对象, ...)。查询单条可用 findOne 或 findById;字段筛选用 select({ title: 1, author: 1, _id: 0 }) 后接 exec;排序用 sort({ hot: 1 });截取用 skip(0)、limit(10)。
易错点:没有 delete 方法,只有 deleteOne/deleteMany;deleteOne 只删满足条件的第一条;_id 是下划线开头,模板里写 item._id;find 返回数组,findOne 返回对象;select 中 _id 默认会带出,除非设 _id: 0;skip 从 0 开始计数。转写稿里批量插入用数组,JSON 文件可通过 require 导入并自动转成对象,这也是调试时要记住的规则。
课堂画面






代码示例
1. 记账本项目:连接数据库 + 创建 Schema 和 Model
js
// ===== bin/www =====
// 只在启动时连接一次数据库;连接成功后才启动 HTTP 服务
const mongoose = require('mongoose');
// 第二步:建立连接(库不存在会自动创建)
mongoose.connect('mongodb://127.0.0.1:27017/account-project');
// 第三步:监听连接失败事件
mongoose.connection.on('error', function (err) {
console.log('数据库连接失败,整个应用无法启动,请排查错误', err);
});
// 第三步:监听连接成功事件
mongoose.connection.once('open', function () {
// 以下为原来 bin/www 里启动服务的代码,整体挪进成功回调里
const port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
const server = http.createServer(app);
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
});js
// ===== models/account.js =====
// 一个集合对应一个文件;这个模块只负责“建 Schema + 建 Model 并暴露”
const mongoose = require('mongoose'); // 每个模块有自己的作用域,都要单独导入
// 第四步:创建 Schema(规定文档里有哪些字段、什么类型)
const accountSchema = new mongoose.Schema({
title: String, // 事项
time: String, // 时间(前端用 moment 处理成字符串)
type: Number, // 类型
account: Number // 金额
});
// 第五步:创建 Model,第一个参数是集合名,第二个是 Schema
// 把 Model 对象作为暴露数据,谁需要增删改查谁就 require 它
module.exports = mongoose.model('accounts', accountSchema);要点与易错点
- 连接只需一次,必须写在
bin/www(启动文件)里,不能写在路由回调里,否则每次请求都会新建一个连接。 - 顺序是“先连库、再启服务”:把原来的
server.listen(...)整段剪切到connection.once('open')的回调中;连不上库就没必要启动 HTTP 服务。 - 监听事件用
mongoose.connection.on('error')和mongoose.connection.once('open'),注意connection是小写属性。 models/account.js里要单独require('mongoose'),不能指望bin/www里导入过就能用。mongoose.model('accounts', schema)的集合名建议用复数(对应数据库里的accounts);Model 暴露出去后,别处拿到的是 Model 对象而不是 Schema。- 安装依赖时用
mongoose@6(npm i mongoose@6),版本不同 API 有差异。
2. Mongoose 增删改查完整演示脚本
js
// 运行:node 02_crud.js
const mongoose = require('mongoose');
mongoose.connect('mongodb://127.0.0.1:27017/project04');
const songSchema = new mongoose.Schema({
author: String,
language: String,
hot: Number,
title: String
});
const SongModel = mongoose.model('songs', songSchema);
mongoose.connection.once('open', async () => {
// ===== 一、批量插入 =====
const data = require('./data.json'); // json 当模块导入,得到的是「对象」
let res = await SongModel.create(data.song_list); // 要 .song_list 才是数组
console.log('插入成功,共', res.length, '条');
// ===== 二、删除 =====
// deleteOne:只删除满足条件的「第一个」文档
let r = await SongModel.deleteOne({ author: '林飞如' });
console.log('deleteOne 删除数量', r.deletedCount);
// deleteMany:删除满足条件的「所有」文档
r = await SongModel.deleteMany({ author: '林飞如' });
console.log('deleteMany 删除数量', r.deletedCount);
// 条件传空对象 = 清空集合
// r = await SongModel.deleteMany({});
// ===== 三、更新 =====
// updateOne:只改第一个,且只改指定字段,不会用新对象覆盖整个文档
r = await SongModel.updateOne({ author: '蔡徐坤' }, { author: '高乐高' });
console.log('updateOne 更新数量', r.modifiedCount);
// updateMany:改所有满足条件的
r = await SongModel.updateMany({ author: '蔡徐坤' }, { author: '林飞如' });
console.log('updateMany 更新数量', r.modifiedCount);
// ===== 四、查询 =====
const one = await SongModel.findOne({ author: '高乐高' }); // 单条 -> 对象
console.log('findOne', one);
const byId = await SongModel.findById('64a1b2c3d4e5f6a7b8c9d0e1'); // 按 ID 查单条
console.log('findById', byId);
const some = await SongModel.find({ author: '高乐高' }); // 多条 -> 数组
console.log('find 多条数量', some.length);
const all = await SongModel.find(); // 不写条件 = 全部
console.log('全部数量', all.length);
// 字段筛选 + 排序 + 截取
const part = await SongModel
.find({ hot: { $gt: 500 } }) // 条件:热度大于 500
.select({ title: 1, hot: 1, _id: 0 }) // 1 表示要,0 表示不要(_id 默认会带)
.sort({ hot: -1 }) // 1 升序,-1 降序
.skip(0) // 从第几条开始,从 0 开始算
.limit(10) // 取多少条
.exec(); // 用 exec 执行
console.log('筛选排序截取', part);
mongoose.disconnect(); // 演示脚本才需要关连接,实际项目里不关
});要点与易错点
require('./data.json')得到的是对象,要取data.song_list;写create([])就真插入空数组了。- 批量插入用
create(数组),返回的res是插入后的文档数组,所以会打印一大片。 - 删除没有
delete方法,只有deleteOne/deleteMany;deleteOne只删第一个,返回值是结果对象,deletedCount才是数量。 - 更新用
updateOne/updateMany;因为有 Schema 约束,它只改你给的字段,不会用新对象覆盖整个文档。 find()不写条件就是查全部;findOne返回对象,find返回数组。- 字段筛选要写成
find(条件).select({...}).exec()这种链式调用,_id默认一定返回,想排除必须显式写_id: 0。 sort里1升序、-1降序;skip是从 0 开始的下标。- 演示脚本里不
disconnect(),Node 进程会一直挂着不退出——这是正常的,实际项目里正需要这样。
3. 记账本项目:在路由里用 Model 做增删查
js
// ===== routes/account.js =====
const express = require('express');
const router = express.Router();
// 第三步:导入 models/account.js,拿到的是 Model 对象
const AccountModel = require('../models/account');
// 首页:查询全部账单
router.get('/', function (req, res) {
AccountModel.find(function (err, data) {
if (err) {
res.status(500).send('数据库读取失败');
return;
}
// data 是数组,直接传给模板
res.render('index', { list: data });
});
});
// 添加账单:req.body 正好就是 Schema 里那几个字段,_id 自动生成
router.post('/create', function (req, res) {
AccountModel.create(req.body, function (err) {
if (err) {
res.render('fail', { title: '账单添加失败', url: '/create' });
return;
}
res.render('success', { title: '账单添加成功', url: '/' });
});
});
// 删除账单:条件用 _id
router.get('/delete/:id', function (req, res) {
const { id } = req.params;
AccountModel.deleteOne({ _id: id }, function (err) {
if (err) {
res.render('fail', { title: '账单删除失败', url: '/' });
return;
}
res.render('success', { title: '账单删除成功', url: '/' });
});
});
module.exports = router;html
<!-- views/index.ejs 里渲染列表的关键一行:用 item._id,不是 item.id -->
<a href="/delete/<%= item._id %>">删除</a>要点与易错点
- 路由里
require('../models/account')得到的是 Model 对象,增删改查全部基于它,不再需要 lowdb 和手动生成 id。 - 添加时直接
create(req.body)即可,_id由 MongoDB 自动生成,不要自己造 ID。 - 模板里取 ID 必须写
item._id(带下划线),写item.id拿不到值,删除链接就带不上 ID。 - 删除条件要写成
{ _id: id },id是字符串也没关系,Mongoose 会自动转成 ObjectId。 - 删除/更新失败时
res.render('fail', { title, url }),把标题和跳转地址传给模板复用同一个失败页。 - 常见报错
xxxModel is not defined,基本都是变量名忘了改;命令行里的报错不友好,要学会顺着信息定位。 - 想“假删”可以在 Schema 里加
isDelete: { type: Boolean, default: false },删除时改这个字段,查询时只查isDelete: false。
常见错误
1. 脚本跑完卡住不退出
- 现象:
node 02.js数据已插入成功,但命令行光标一直停着不返回。 - 原因:Mongoose 与 MongoDB 的长连接没有关闭,Node 进程认为还有活跃句柄。
- 修法:演示/脚本场景在回调末尾加
mongoose.connection.close();真实后端服务不要关——服务需要长期待机接请求,连接断了就没法查库。所以这个报错在项目里属于"正常现象",别乱加 close。
2. 批量插入报 xxxModel is not defined
- 现象:复制上一份代码改成批量插入,一运行就抛引用错误。
- 原因:复制后只改了 Schema/Model 的定义,忘了改
new mongoose.model(...)存下来的变量名,调用处还在用旧的UserModel。 - 修法:养成"改一处、搜一遍"的习惯:改完 model 变量名后全文搜索旧名字;命令行报错先看最后一行的变量名/文件行号,不要一报错就重写。
3. delete is not a function
- 现象:调用
Model.delete(条件, cb)直接报方法不存在。 - 原因:Mongoose 没有
delete这个方法,只有区分单条/多条的deleteOne/deleteMany。 - 修法:删一条用
deleteOne,删全部匹配用deleteMany;顺带记住deleteOne只删第一个匹配文档,返回的是{ deletedCount }结果对象,不是被删的数据。
4. 加了 select() 之后回调不执行
- 现象:
find(条件).select({...})(回调)或直接跟回调,什么也没打印。 - 原因:
select/sort/skip/limit都是链式查询构造器,它们返回的仍是 Query 对象,不负责执行;把回调塞给select是无效的。 - 修法:整条链写完,最后统一
.exec(callback)执行。链式调用是"先描述、后执行"。
5. 分页少一条 / 首条丢失
- 现象:
skip(90).limit(10)拿到的不是第 91~100 条,或者总数对不上。 - 原因:
skip的参数是索引,从 0 开始计数,不是从 1 开始。 - 修法:要第 91 条起,写
skip(90);分页公式skip((page-1)*pageSize)。
6. 删除按钮点了没反应 / id 传不过去
- 现象:模板里
href="/account/delete?id={{item.id}}",后端拿到的 id 是空。 - 原因:MongoDB 文档主键是
_id,不是id;模板取错字段,链接里就没有值。 - 修法:模板里统一写
item._id;后端用req.query.id拼成{_id: id}作为删除条件。
7. 数据库还没连上就启动了 HTTP 服务
- 现象:服务能起来,但一访问就报查询超时/空数据。
- 原因:
app.listen写在了连接代码外面,连接是异步的,服务先跑起来了。 - 修法:把
listen整段代码搬进mongoose.connection.on('open', ...)的回调里;连接失败事件里只打印错误并退出,不启动服务。
8. 在路由里连数据库
- 现象:本地测没问题,一上线连接数暴涨、服务变慢。
- 原因:连接代码写进了路由回调,每个请求都新建一次连接。
- 修法:整个应用只连一次,放在启动入口(
bin/www),路由层只做Model.xxx()的增删改查。
练习
批量插入 + 字段裁剪:用
require导入一个 JSON 文件(内含数组),向新集合批量插入数据,Schema 只声明 5 个字段。运行后到图形化工具里确认:多余字段是否被丢弃?_id是否自动生成?方法选型:分别用
deleteOne、deleteMany({})清空数据,观察deletedCount;再用node 02.js把数据恢复回来,体会"数据是代码生成的"这件事。链式查询:查出"热度 > 500"的数据,只要
title和hot两个字段(不要_id),按hot降序,取第 11~20 条。写完后故意把.exec()去掉,看会发生什么,再改回来。项目改造:把记账本的
lowdb全部替换为 Mongoose —— 连接放到启动入口、models/account.js只导出 Model、路由里用 Model 完成增/查/删,并让首页列表和删除链接都正常工作。思考题(写出方案,不必编码):产品要求"删除后 30 天内可恢复"。说明你会怎么改 Schema 和查询条件,以及首页列表、删除接口各需要改动哪里。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | mongoose-添加数据 | 10:21 |
| 2 | mongoose-删除数据 | 05:48 |
| 3 | mongoose-更新数据 | 04:42 |
| 4 | mongoose-查询数据 | 13:42 |
| 5 | 记账本项目升级-连接数据库 | 15:10 |
| 6 | 记账本项目升级-创建schema和model | 06:04 |
| 7 | 记账本项目升级-实现增删查 | 16:42 |
小结
- 增删改查四个动作的写法高度同构:条件对象 + 回调(err, result),把套路记住比记方法名更划算。
- 单条与多条是两个不同方法(
findOne/find、deleteOne/deleteMany、updateOne/updateMany),选错方法的后果是"只改了一条"或"误伤全表"。 - 空对象
{}是合法条件,意味着"全部"——它既是清库的利器,也是线上事故的常见来源,写之前先想清楚。 - Schema 是白名单:没声明的字段进不了库,这让更新变成"局部修改"而不是"整体覆盖",比原生命令行更安全。
- 链式查询(select / sort / skip / limit)只是构造,
exec才执行;_id默认返回,要排除就显式置 0。 - 连接的生命周期应与应用一致:启动时连一次、连接成功后再监听端口、失败就不启动,绝不在路由里连。
- 模型文件只负责"定义 Schema + 创建 Model + 导出 Model",谁需要谁导入;这一层分离让后续加用户集合、加登录时不用动路由结构。
- 真实业务几乎不做物理删除,多用标记位做逻辑删除——留数据就是留退路,也是后续做恢复、审计、对账的前提。