Mongoose如何查询指定教师并嵌套关联课程、学生及爱好数据
Mongoose 多级嵌套关联查询解决方案
首先需要先将给出的Schema编译为Mongoose模型:
const Teacher = mongoose.model('Teacher', teacherSchema) const Course = mongoose.model('Course', courseSchema) const Student = mongoose.model('Student', studentSchema) const Hobby = mongoose.model('Hobby', hobbySchema)
共有两种常用实现方案:
方案1:虚拟字段 + 多级populate(推荐)
由于给出的Schema都是子级存储父级外键,属于反向关联场景,需要先给各个Schema添加虚拟字段做反向映射,同时开启虚拟字段序列化配置:
// 给教师Schema添加虚拟课程字段 teacherSchema.virtual('courses', { ref: 'Course', localField: '_id', foreignField: 'teacher' }) // 给课程Schema添加虚拟学生字段 courseSchema.virtual('students', { ref: 'Student', localField: '_id', foreignField: 'course' }) // 给学生Schema添加虚拟爱好字段 studentSchema.virtual('hobbies', { ref: 'Hobby', localField: '_id', foreignField: 'student' }) // 开启虚拟字段转JSON配置,确保接口返回时包含虚拟字段 teacherSchema.set('toJSON', { virtuals: true }) courseSchema.set('toJSON', { virtuals: true }) studentSchema.set('toJSON', { virtuals: true })
配置完成后就可以直接通过多级populate查询,以Express路由为例:
router.get('/teachers/:id', async (req, res) => { try { // 先校验ID合法性 if (!mongoose.Types.ObjectId.isValid(req.params.id)) { return res.status(400).json({ message: '无效的教师ID' }) } const teacher = await Teacher.findById(req.params.id) // 嵌套populate关联数据 .populate({ path: 'courses', populate: { path: 'students', populate: { path: 'hobbies' } } }) if (!teacher) { return res.status(404).json({ message: '未找到对应教师' }) } res.json(teacher) } catch (err) { res.status(500).json({ message: '查询失败', error: err.message }) } })
该方案代码简洁易维护,适合经常需要做反向关联查询的业务场景。
方案2:聚合管道(无需修改Schema)
如果不想改动原有Schema添加虚拟字段,可以直接用MongoDB聚合管道实现多级关联:
router.get('/teachers/:id', async (req, res) => { try { if (!mongoose.Types.ObjectId.isValid(req.params.id)) { return res.status(400).json({ message: '无效的教师ID' }) } const teachers = await Teacher.aggregate([ // 匹配指定ID的教师 { $match: { _id: mongoose.Types.ObjectId(req.params.id) } }, // 关联查询教师名下课程 { $lookup: { from: 'courses', localField: '_id', foreignField: 'teacher', as: 'courses' } }, // 展开课程数组,方便后续关联学生,保留无课程的教师数据 { $unwind: { path: '$courses', preserveNullAndEmptyArrays: true } }, // 关联课程对应的学生 { $lookup: { from: 'students', localField: 'courses._id', foreignField: 'course', as: 'courses.students' } }, // 展开学生数组,关联爱好,保留无学生的课程数据 { $unwind: { path: '$courses.students', preserveNullAndEmptyArrays: true } }, { $lookup: { from: 'hobbies', localField: 'courses.students._id', foreignField: 'student', as: 'courses.students.hobbies' } }, // 合并同课程的学生数据 { $group: { _id: { teacherId: '$_id', courseId: '$courses._id' }, teacherName: { $first: '$name' }, courseBaseInfo: { $first: '$courses' }, students: { $push: '$courses.students' } } }, // 合并同教师的课程数据 { $group: { _id: '$_id.teacherId', name: { $first: '$teacherName' }, courses: { $push: { $mergeObjects: ['$courseBaseInfo', { students: '$students' }] } } } } ]) if (teachers.length === 0) { return res.status(404).json({ message: '未找到对应教师' }) } res.json(teachers[0]) } catch (err) { res.status(500).json({ message: '查询失败', error: err.message }) } })
该方案不需要修改原有Schema,适合临时一次性查询的场景。
注意事项
- 如果需要过滤返回字段,可以在populate中添加
select: '字段1 字段2'配置,或者在聚合中添加$project阶段,避免返回冗余数据 preserveNullAndEmptyArrays: true配置可以保留没有学生的课程、没有爱好的学生,不会被自动过滤- 生产环境建议添加参数校验和错误捕获,避免非法ID导致服务报错
内容的提问来源于stack exchange,提问作者Sagspot
相关产品推荐
相关产品推荐

