尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Mongoose MongoDB ODM 实战指南:Schema、Model、连接与中间件的完整使用手册

Mongoose MongoDB ODM 实战指南:Schema、Model、连接与中间件的完整使用手册 Mongoose MongoDB ODM 实战指南Schema、Model、连接与中间件的完整使用手册【免费下载链接】mongooseMongoDB object modeling designed to work in an asynchronous environment.项目地址: https://gitcode.com/GitHub_Trending/mo/mongooseMongoose 是为异步环境设计的 MongoDB 对象建模工具ODM它通过 Schema 为 MongoDB 文档定义结构与数据类型并提供校验、默认值、中间件、索引、嵌入文档伪 JOIN等一整套封装能力。本文以 Mongoose 9.x 源码仓库README.md为核心骨架结合 lib/mongoose.js、lib/connection.js、lib/helpers/pluralize.js 等实现文件系统讲解安装导入、连接管理、Schema/Model 定义、嵌入式文档、中间件机制与底层驱动访问帮助你写出可复现、可深入排查的 Mongoose 应用代码。一、项目概览与版本现状Mongoose 是一个构建在 MongoDB 之上的对象建模工具专为异步环境设计同时支持 Node.js 与 Denoalpha 阶段。根据仓库 package.json 的声明当前仓库对应的版本为9.9.5运行环境要求 Node.js 20.19.0底层依赖官方 MongoDB Node.js 驱动mongodb ~7.5并依赖kareem中间件内核、mquery查询构造器、mpath路径操作、ms、sift等模块。从入口文件 index.js 可以看到require(mongoose)实际导出的是 lib/mongoose.js 中new Mongoose()创建的默认实例该实例同时通过module.exports.default与module.exports.mongoose支持 ESM 风格导入。Mongoose 能为你做什么除定义文档结构与数据类型外Schema 还统一管理以下能力对应 README.md 中的清单均可结合仓库源码进一步学习校验器同步与异步见 docs/validation.md默认值DefaultsGetters 与 Setters索引Indexes见 docs/guide.md中间件Middleware见 docs/middleware.md方法与静态方法的定义插件机制Plugins见 docs/plugins.md伪 JOINPopulate见 docs/populate.md二、安装与导入2.1 前置条件首先安装 Node.js 与 MongoDB然后使用任意主流包管理器安装mongoose包# npm npm install mongoose # pnpm pnpm add mongoose # Yarn yarn add mongoose # Bun bun add mongoose2.2 导入方式// 使用 Node.js require() const mongoose require(mongoose); // 使用 ES6 imports import mongoose from mongoose;在 Deno 中可利用 Deno 的createRequire()加载 CommonJS 模块import { createRequire } from https://deno.land/std0.177.0/node/module.ts; const require createRequire(import.meta.url); const mongoose require(mongoose); mongoose.connect(mongodb://127.0.0.1:27017/test) .then(() console.log(Connected!));运行上述 Deno 脚本时需要显式授予网络、读写、系统与环境变量等权限deno run --allow-net --allow-read --allow-sys --allow-env mongoose-test.js仓库内的 test/deno.mjs 同样通过import { createRequire } from node:module与createRequire(import.meta.url)的方式在 Deno 环境中加载 Mongoose可作为实际参考。三、连接 MongoDB3.1 connect 与 createConnection 的选择如果应用只使用一个数据库直接调用mongoose.connect如果需要额外连接则使用mongoose.createConnection。两者都接受mongodb://URI或host, database, port, options形式的参数await mongoose.connect(mongodb://127.0.0.1/my_database);从 lib/mongoose.js 的connect()实现L464-L476可以看到connect()最终委托给默认连接的conn.openUri(uri, options)成功后 resolve 为mongoose实例本身若连接失败例如服务器不可达则会抛出MongooseServerSelectionError: Server selection timed out after 30000 ms。连接成功后Connection实例上会触发open事件。使用mongoose.connect时该Connection就是mongoose.connection使用mongoose.createConnection时返回值就是对应的Connection。从源码看mongoose.connection实际上是connections[0]的 getterlib/mongoose.js而每次调用createConnection()都会创建一个新的Connection并 push 进connections数组L409-L423。注意如果本地连接失败尝试用127.0.0.1代替localhost——有时本机 hostname 被修改会导致解析异常。3.2 连接缓冲机制重要Mongoose 会把所有命令缓冲起来直到连接成功。这意味着你不需要等待连接完成就可以先定义模型、执行查询等操作。该缓冲行为由连接选项bufferCommands默认true与bufferTimeoutMS默认10000即 10 秒控制如果 10 秒内仍未连接成功缓冲的操作会抛出错误。3.3 连接相关选项结合 lib/mongoose.js 中connect()与createConnection()的 JSDoc 注释常用连接选项如下选项默认值说明bufferCommandstrue是否对该连接上的所有模型启用缓冲机制bufferTimeoutMS10000缓冲超时时间毫秒超时后报错dbName取自连接串指定要使用的数据库名user/pass无认证用户名/密码等价于auth.username/auth.passwordmaxPoolSize100驱动保持打开的最大 socket 数每个 socket 同一时刻只能执行一个操作minPoolSize0最小 socket 数serverSelectionTimeoutMS30000服务器选择超时时间heartbeatFrequencyMS无心跳间隔建议不要低于1000autoIndextrue是否自动创建索引autoCreatefalse是否在创建模型时自动调用createCollection()测试事务、变更流等场景需要socketTimeoutMS0socket 空闲超时0表示不超时family0透传给 Nodedns.lookup()0双栈、4仅 IPv4、6仅 IPv6四、定义 Schema 与 Model4.1 基础 Schema模型通过Schema接口定义const Schema mongoose.Schema; const ObjectId Schema.ObjectId; const BlogPost new Schema({ author: ObjectId, title: String, body: String, date: Date });4.2 带约束的字段Schema 字段支持类型、默认值、校验、索引等配置const Comment new Schema({ name: { type: String, default: hahaha }, age: { type: Number, min: 18, index: true }, bio: { type: String, match: /[a-z]/ }, date: { type: Date, default: Date.now }, buff: Buffer }); // 一个 setter Comment.path(name).set(function(v) { return capitalize(v); }); // 中间件 Comment.pre(save, function(next) { notify(this.get(email)); next(); });Mongoose 内置的 SchemaType 集合定义在 lib/schema/index.js包括Array、BigInt、Boolean、Buffer、Date、Decimal128、DocumentArray、Double、Int32、Map、Mixed、Number、ObjectId、String、Subdocument、UUID等并提供Oid、Object、Bool、ObjectID等别名L8-L32。例如Object等价于MixedObjectID等价于ObjectId。五、访问与使用 Model5.1 定义与获取通过mongoose.model(ModelName, mySchema)定义模型后可以用同一个函数获取const MyModel mongoose.model(ModelName);或者一步到位const MyModel mongoose.model(ModelName, mySchema);第一个参数是集合名称的单数形式。Mongoose 会自动查找模型名的复数版本。例如const MyModel mongoose.model(Ticket, mySchema);MyModel将使用tickets集合而不是ticket集合。该复数化逻辑由 lib/helpers/pluralize.js 实现它维护了一套完整的复数规则表如(m|wom)an→$1en、(child)$→$1ren、(octop|cact|foc|fung|nucle)us→$1i等L9-L34以及不可数名词表species、series、fish、sheep、moose、deer、news等L44-L72。因此Ticket会正确复数为tickets而News保持news不变。测试用例 test/index.test.js如legacy pluralize by default (gh-5958)验证了默认复数化行为你也可以通过mongoose.pluralize(customFn)覆盖默认复数化函数对应 lib/mongoose.js。5.2 实例化、保存与查询const instance new MyModel(); instance.my.key hello; await instance.save();或从同一集合查找文档await MyModel.find({});此外还可以使用findOne、findById、update等方法const instance await MyModel.findOne({ /* ... */ }); console.log(instance.my.key); // hello查询相关的更多细节见 docs/queries.md。5.3 独立连接的模型陷阱重要如果使用mongoose.createConnection()打开了独立连接却仍通过mongoose.model(ModelName)访问模型将不会按预期工作——因为它没有挂接到活动的数据库连接上。此时应通过你创建的那个连接来访问模型const conn mongoose.createConnection(your connection string); const MyModel conn.model(ModelName, schema); const m new MyModel(); await m.save(); // 正常工作与之对比const conn mongoose.createConnection(your connection string); const MyModel mongoose.model(ModelName, schema); const m new MyModel(); await m.save(); // 不工作因为默认连接对象从未被连接从源码看mongoose.model()会把模型注册到默认连接的_mongoose.connection.models[name]lib/mongoose.js而conn.model()则注册在独立连接上两者互不相通这正是上述行为差异的根源。六、嵌入式文档Embedded Documents在前面的示例中Schema 里可以定义类似comments: [Comment]的键其中Comment是我们创建的Schema。这意味着创建嵌入式文档非常简单// 获取模型 const BlogPost mongoose.model(BlogPost); // 创建一篇博客文章 const post new BlogPost(); // 添加一条评论 post.comments.push({ title: My comment }); await post.save();删除嵌入式文档同样简单const post await BlogPost.findById(myId); post.comments[0].deleteOne(); await post.save();嵌入式文档子文档享受与模型完全相同的能力默认值、校验器、中间件等一应俱全。实现层面[Comment]会编译为 DocumentArray见 lib/schema/documentArray.js每个元素都是独立的子文档实例拥有自己的修改跟踪与校验逻辑。七、中间件Middleware中间件是 Mongoose 最强大的扩展点之一完整说明见 docs/middleware.md。这里聚焦 README 重点讲解的两种能力。7.1 拦截并修改方法参数你可以通过中间件拦截方法参数。例如每当文档中某个路径被set为新值时广播文档的变更schema.pre(set, function(next, path, val, typel) { // this 是当前 Document this.emit(set, path, val); // 将控制权交给下一个 pre 钩子 next(); });更进一步你可以在中间件中修改传入的方法参数使后续中间件看到不同的值——只需把新值传给nextschema.pre(method, function firstPre(next, methodArg1, methodArg2) { // 修改 methodArg1 next(altered- methodArg1.toString(), methodArg2); }); // pre 声明是可链式调用的 schema.pre(method, function secondPre(next, methodArg1, methodArg2) { console.log(methodArg1); // altered-originalValOfMethodArg1 console.log(methodArg2); // originalValOfMethodArg2 // 不传参数给 next 会自动沿用当前参数值 // 即下面的 next() 等价于 next(methodArg1, methodArg2) // 也等价于 next(altered-originalValOfMethodArg1, originalValOfMethodArg2) next(); });Mongoose 的中间件内核是kareem见 package.json 的依赖声明Schema.prototype.pre/Schema.prototype.post定义于 lib/schema.jsL2164、L2220。此外lib/mongoose.js 还暴露了三个与中间件协作的辅助函数mongoose.skipMiddlewareFunction(result)在pre()钩子中跳过被包裹的函数等价于Kareem.skipWrappedFunctionL1333mongoose.overwriteMiddlewareResult(result)在post()钩子中完全替换返回值L1352mongoose.overwriteMiddlewareArguments(...args)在pre()钩子中替换传给下一个中间件的参数L1389。7.2 Schema 中type的陷阱type在 Schema 中具有特殊含义。如果 Schema 需要把type作为嵌套属性使用必须使用对象字面量表示法new Schema({ broken: { type: Boolean }, asset: { name: String, type: String // 坏了asset 会被解释成 String } }); new Schema({ works: { type: Boolean }, asset: { name: String, type: { type: String } // 正常asset 是一个带 type 属性的对象 } });这正是 lib/schema/index.js 中SchemaType解析机制的一部分顶层type键被当作类型声明消耗只有嵌套在{ type: ... }内才能表达名为 type 的字段。八、驱动访问Driver AccessMongoose 构建在官方 MongoDB Node.js 驱动之上依赖声明见 package.json 的mongodb: ~7.5。每个 Mongoose 模型都持有一个原生 MongoDB 驱动集合的引用可通过YourModel.collection访问。但需要特别警惕直接使用 collection 对象会绕过所有 Mongoose 特性包括钩子hooks、校验validation等。唯一的例外是YourModel.collection仍然会缓冲命令因此YourModel.collection.find()不会返回游标cursor。从源码结构看驱动抽象层位于 lib/drivers/node-mongodb-native其中 collection.js 与 connection.js 负责把 Mongoose 的接口适配到原生驱动。日常开发中应优先使用Model.find()、Model.findOne()等 Mongoose 查询 API仅在需要底层能力时才接触Model.collection。九、全局选项mongoose.set()虽然不是 README 的正文示例但mongoose.set()是日常调优的高频 API其完整选项清单记录在 lib/mongoose.js 的 JSDoc 中L222-L249此处摘录与生产实践强相关的核心选项选项默认值说明debugfalse为true时把发给 MongoDB 的操作打印到控制台也支持对象color、shell、timestamp、可写流或自定义回调函数autoIndextrue是否自动创建索引autoCreatetrue创建模型时是否自动调用Model.createCollection()bufferCommandstrue全局缓冲机制开关bufferTimeoutMS10000缓冲超时时间stricttrue全局 strict 模式可为false/true/throwstrictQueryfalse查询过滤器的 strict 模式runValidatorsfalse是否默认启用 update validatorssanitizeFilterfalse是否对查询过滤器做选择器注入防护把以$开头的嵌套对象包进$eqreturnDocumentbeforefindOneAndUpdate()等方法的默认返回值见 docs/tutorials/findoneandupdate.mdmaxTimeMS无为每个查询附加maxTimeMSoverwriteModelsfalse同名模型默认覆盖而非抛出OverwriteModelErrortimestamps.createdAt.immutabletrue设为false时允许更新createdAt字段用法示例mongoose.set(debug, true); // 打印数据库操作日志 mongoose.set({ autoIndex: false, strictQuery: true }); // 一次设置多个选项对应的 getter 是mongoose.get(key)lib/mongoose.js。底层实现会对非法选项名抛出SetOptionError并收集所有非法键一次性报错。十、其他常用 APImongoose.disconnect()并行关闭所有连接lib/mongoose.js不再接受回调参数。mongoose.startSession()等价于mongoose.connection.startSession()用于获取 MongoDB 会话以支持因果一致性、可重试写入与事务L514-L518。mongoose.deleteModel(name)从默认连接移除模型可用于测试中清理模型以避免OverwriteModelError支持正则批量删除L736-L742。mongoose.modelNames()返回本实例上创建的模型名数组不包含connection.model()创建的模型L755-L760。mongoose.isValidObjectId(v)与mongoose.isObjectIdOrHexString(v)前者判断值能否被转换为 ObjectId0123456789ab、数字也返回true后者更严格仅对 ObjectId 实例或 24 位十六进制字符串返回trueL1114-L1148。mongoose.sanitizeFilter(obj)与mongoose.trusted(obj)前者将含$前缀键的嵌套对象包进$eq以防御查询选择器注入后者标记已知可信的查询选择器跳过净化L1295-L1315。mongoose.omitUndefined(obj)删除值为undefined的键避免find({ name: undefined })被当作{ name: null }处理L1411。十一、生态与学习路径官方 API 文档由仓库的 docs/api.md 承载更细分的主题指南位于 docs/guide.md、docs/models.md、docs/queries.md、docs/validation.md、docs/middleware.md、docs/populate.md、docs/plugins.md 等文件。仓库 test/ 目录提供了数百个测试用例例如 test/model.querying.test.js、test/query.test.js、test/types.documentarray.test.js是理解实际行为的最佳参考。若需在本地运行测试可在安装 MongoDB 后执行npm testnpm run test-rs则针对副本集场景运行脚本定义见 package.json。结语从安装导入、连接管理到 Schema/Model、嵌入式文档与中间件Mongoose 用一套统一的接口把 MongoDB 的灵活性与应用的约束性结合起来。理解mongoose.connection与createConnection()的区别、复数化集合名的来源、type关键字的解析规则以及Model.collection的绕过风险是避免线上踩坑的关键。配合本文引用的 lib/mongoose.js、lib/connection.js、lib/schema/index.js 等源码文件你可以进一步追踪任意 API 的真实调用链。【免费下载链接】mongooseMongoDB object modeling designed to work in an asynchronous environment.项目地址: https://gitcode.com/GitHub_Trending/mo/mongoose创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表