如何用Apollo GraphQL Query从两个关联文档获取数据?
当然可行!这是GraphQL里非常典型的关联数据查询场景,Apollo不管是服务端还是客户端都能完美支持这种跨文档的关联获取需求。我来给你拆解下具体怎么实现:
1. 先定义你的GraphQL Schema
首先你需要在Schema里明确两个文档的类型,并且给DocumentA添加一个关联字段(比如senders),用来关联到DocumentB的信息:
type DocumentA { _id: ID! senders_ID: [String!]! # 对应你文档A里的sender_id数组 senders: [DocumentB!]! # 新增的关联字段,用来获取每个sender对应的firstname } type DocumentB { sender_id: String! firstname: String! } type Query { getDocumentA(id: ID!): DocumentA # 单个查询 getAllDocumentA: [DocumentA!]! # 查询所有文档A }
2. 编写Resolver处理关联查询
Resolver是GraphQL里处理数据获取的核心部分,我们需要在DocumentA的senders字段Resolver里,根据父对象(也就是当前的DocumentA实例)的senders_ID数组,去查询DocumentB对应的记录。
举个用MongoDB + Mongoose的例子:
const DocumentA = require('./models/DocumentA'); const DocumentB = require('./models/DocumentB'); const resolvers = { // 处理DocumentA的senders关联字段 DocumentA: { async senders(parent) { // 用$in操作符批量查询所有匹配的sender_id return await DocumentB.find({ sender_id: { $in: parent.senders_ID } }); } }, // 基础查询Resolver Query: { async getDocumentA(_, { id }) { return await DocumentA.findById(id); }, async getAllDocumentA() { return await DocumentA.find(); } } };
3. 客户端发起查询
现在你就可以在Apollo客户端里写一条查询,一次性拿到DocumentA的所有信息,以及每个sender对应的firstname了:
query GetTargetDocumentA($id: ID!) { getDocumentA(id: $id) { _id senders_ID senders { sender_id firstname } } }
4. 性能优化:避免N+1查询问题
如果你的数据量比较大,上面的基础实现可能会遇到N+1查询的问题(每个DocumentA实例都会触发一次DocumentB的查询)。这时候可以用Apollo官方推荐的DataLoader来批量处理查询:
const { DataLoader } = require('dataloader'); // 创建DataLoader实例,批量加载sender信息 const senderLoader = new DataLoader(async (senderIds) => { const senders = await DocumentB.find({ sender_id: { $in: senderIds } }); // 保证返回的结果顺序和传入的senderIds一致 return senderIds.map(id => senders.find(sender => sender.sender_id === id)); }); // 更新Resolver使用DataLoader const resolvers = { DocumentA: { async senders(parent) { // 批量加载每个sender_id对应的DocumentB return await Promise.all(parent.senders_ID.map(id => senderLoader.load(id))); } }, // ...其他Resolver };
这样就能把多次查询合并成一次,大大提升性能。
总的来说,核心就是利用GraphQL的Resolver机制,在服务端处理跨文档的关联逻辑,客户端只需要一次查询就能拿到所有需要的数据,完全满足你遍历DocumentA的sender_id并获取对应firstname的需求。
内容的提问来源于stack exchange,提问作者EyTa
相关产品推荐
相关产品推荐

