MongoDB MongooseJS 存储层级文件夹时如何获取父ID关联子文档且保障性能
解决方案
核心思路是分层级批量插入,避免单条递归插入产生的大量数据库IO请求,既可以正确维护父子节点的ID关联,也能保证插入性能。
实现逻辑
你的预设文件夹结构是固定层级的树形结构,我们可以先把整棵树按层级拆分,从最顶层(无父节点)开始,逐层向下插入:
- 先预处理整棵树,给每个节点分配唯一临时ID,同时记录每个节点的父节点临时ID、所属层级,最后把所有节点按层级分组
- 从最低层级(顶层,层级为0)开始批量插入,插入完成后记录临时ID和数据库返回的真实_id的映射关系
- 插入下一层级时,直接通过映射表拿到父节点的真实_id作为parentId字段值,再批量插入当前层级
- 以此类推直到所有层级插入完成,整个过程的数据库IO次数等于树形结构的最大深度,性能远高于单条递归插入
代码示例
const mongoose = require('mongoose'); const { v4: uuidv4 } = require('uuid'); // 假设你已经定义了DataRoomFolder模型 const DataRoomFolder = mongoose.model('DataRoomFolder'); // 预处理树形结构:拉平节点、按层级分组、生成临时关联ID function preprocessFolderTree(tree, parentTempId = null, currentLevel = 0, levelGroups = {}) { // 初始化当前层级的分组 if (!levelGroups[currentLevel]) levelGroups[currentLevel] = []; for (const node of tree) { const tempId = uuidv4(); // 存储节点信息,包含临时关联ID levelGroups[currentLevel].push({ tempId, parentTempId, label: node.label }); // 递归处理子节点 if (node.items?.length) { preprocessFolderTree(node.items, tempId, currentLevel + 1, levelGroups); } } return levelGroups; } // 批量插入入口方法 async function createDefaultFolders(projectId, presetFolderTree) { const levelGroups = preprocessFolderTree(presetFolderTree); // 存储临时ID和数据库真实ID的映射 const tempIdToRealId = new Map(); // 按层级从小到大排序,保证父节点先插入 const sortedLevels = Object.keys(levelGroups).map(Number).sort((a, b) => a - b); for (const level of sortedLevels) { const currentLevelNodes = levelGroups[level]; // 构造当前层级要插入的文档 const insertDocs = currentLevelNodes.map(node => ({ isFolder: true, name: node.label, parentId: node.parentTempId ? tempIdToRealId.get(node.parentTempId) : null, projectId: mongoose.Types.ObjectId(projectId) })); // 批量插入当前层级所有节点 const insertedResult = await DataRoomFolder.insertMany(insertDocs); // 更新映射表 currentLevelNodes.forEach((node, index) => { tempIdToRealId.set(node.tempId, insertedResult[index]._id); }); } return tempIdToRealId; } // 调用示例:创建Project后调用即可 const newProject = await Project.create({ /* 你的Project字段 */ }); await createDefaultFolders(newProject._id, 你的预设文件夹结构数组);
注意事项
- 该方案不依赖文件夹名称做关联,即使预设结构里有重名文件夹(比如你示例里的两个
02-Permits)也不会出现关联错误 insertMany默认是有序插入,返回结果的顺序和传入的数组顺序完全一致,所以映射关系不会出错- 如果需要保证数据一致性,可以把整个插入逻辑包裹在Mongoose事务中,插入过程出错时自动回滚所有已插入的文档
内容的提问来源于stack exchange,提问作者AndyCode
相关产品推荐
相关产品推荐

