如何在Prisma批量查询中返回原commentId适配Facebook DataLoader?
实现批量查询同一用户在同一帖子下的评论(配合DataLoader)
针对你的需求,确实Prisma本身没法直接在一次查询中返回原始commentId与对应评论列表的关联,但我们可以通过两次高效的批量查询配合内存映射来实现,同时保持DataLoader的批处理和缓存优势,而且逻辑也不至于太复杂。下面是具体的实现思路和代码:
核心思路
- 先批量获取所有传入
commentId对应的postId,建立commentId -> postId的映射; - 收集唯一的
(postId, userId)组合,批量查询这些组合下的所有评论; - 将查询结果按
postId:userId分组,最后把每个原始请求(commentId, userId)映射到对应的评论列表。
代码实现
import DataLoader from 'dataloader'; import { PrismaClient, Comment } from '@prisma/client'; const prisma = new PrismaClient(); // 定义DataLoader的批量处理函数 const batchFetchCommentsByUserForSamePost = async ( keys: Array<{ commentId: string; userId: string }> ): Promise<Comment[][]> => { // 第一步:批量获取每个commentId对应的postId const commentPostMap = await prisma.comment.findMany({ where: { id: { in: keys.map(k => k.commentId) } }, select: { id: true, postId: true } }); const commentIdToPostId = new Map(commentPostMap.map(item => [item.id, item.postId])); // 收集唯一的(postId, userId)对,避免重复查询 const uniquePairs = Array.from( new Set(keys.map(k => `${commentIdToPostId.get(k.commentId)}:${k.userId}`)) ).map(pair => { const [postId, userId] = pair.split(':'); return { postId, userId }; }); // 第二步:批量查询所有目标(postId, userId)下的评论 const allTargetComments = await prisma.comment.findMany({ where: { OR: uniquePairs.map(pair => ({ postId: pair.postId, writtenBy: { id: pair.userId } })) }, // 按需包含关联字段,比如post、writtenBy include: { post: true, writtenBy: true } }); // 将评论按(postId:userId)分组 const pairToComments = new Map<string, Comment[]>(); allTargetComments.forEach(comment => { const key = `${comment.postId}:${comment.writtenById}`; if (!pairToComments.has(key)) { pairToComments.set(key, []); } pairToComments.get(key)!.push(comment); }); // 映射回原始请求的顺序,返回结果 return keys.map(key => { const postId = commentIdToPostId.get(key.commentId); const mapKey = `${postId}:${key.userId}`; return pairToComments.get(mapKey) || []; }); }; // 创建DataLoader实例,自定义缓存key避免对象哈希冲突 const commentBatchLoader = new DataLoader<{ commentId: string; userId: string }, Comment[]>( batchFetchCommentsByUserForSamePost, { cacheKeyFn: key => `${key.commentId}:${key.userId}` } );
使用方式
在你的解析器中,直接调用这个Loader即可:
// 单个查询示例 const comments = await commentBatchLoader.load({ commentId: 'xxx', userId: 'yyy' }); // 批量查询示例 const multipleResults = await commentBatchLoader.loadMany([ { commentId: 'xxx1', userId: 'yyy1' }, { commentId: 'xxx2', userId: 'yyy2' } ]);
备选方案:原生SQL一次查询(不推荐)
如果你坚持要一次数据库往返,可以用Prisma的原生SQL查询,通过聚合函数将评论列表与commentId、userId关联。但这种方法需要手动处理类型,且不同数据库的聚合语法不同(比如PostgreSQL用JSON_AGG,MySQL用JSON_ARRAYAGG),维护成本较高。示例(以PostgreSQL为例):
const batchFetchRaw = async (keys: Array<{ commentId: string; userId: string }>) => { const placeholders = keys.map((_, idx) => `($${idx*2+1}, $${idx*2+2})`).join(','); const values = keys.flatMap(k => [k.commentId, k.userId]); const rawResult = await prisma.$queryRaw` SELECT c.id as commentId, u.id as userId, JSON_AGG(json_build_object( 'id', com.id, 'text', com.text, 'postId', com."postId", 'writtenById', com."writtenById" )) as comments FROM Comment c JOIN Post p ON c."postId" = p.id JOIN Comment com ON p.id = com."postId" JOIN "User" u ON com."writtenById" = u.id WHERE (c.id, u.id) IN (${placeholders}) GROUP BY c.id, u.id `; const resultMap = new Map(rawResult.map(r => [`${r.commentId}:${r.userId}`, r.comments])); return keys.map(key => resultMap.get(`${key.commentId}:${key.userId}`) || []); };
总结
推荐使用第一种两次批量查询的方案:逻辑清晰、利用Prisma的类型安全优势,且DataLoader会自动帮你处理缓存和批处理,避免N+1问题。虽然是两次数据库请求,但都是批量操作,性能开销远小于N次单条查询。
内容的提问来源于stack exchange,提问作者fodma1
相关产品推荐
相关产品推荐

