数据库对象转GraphQL对象:字段不匹配的Resolver问题求解
先修正你Resolver里的明显错误
首先看你的Query部分代码存在两处基础问题:book resolver错误查询了db.author(应该查db.books);部分函数末尾缺少逗号导致语法错误,先修正:
const resolvers: Resolvers = { Book: { author: async (parent, args, context, info) => { const authors = await db.author.select({ where: { id: parent.author_id } }); return authors[0]; } }, Author: { books: async (parent, args, context, info) => { const books = await db.books.select({ where: { author_id: parent.id } }); return books; } }, Query: { book: async (parent, args, context, info) => { // 修正:查询db.books而非db.author const books = await db.books.select({ where: { id: args.id } }); return books[0]; }, author: async (parent, args, context, info) => { const authors = await db.author.select({ where: { id: args.id } }); return authors[0]; }, books: async (parent, args, context, info) => { const books = await db.books.select(); return books; }, authors: async (parent, args, context, info) => { const authors = await db.authors.select(); return authors; } } };
问题1:解析Book的author字段时,是否必须把author_id加到GraphQL Schema中?
完全不需要。
Resolver里的parent参数是你从数据库查询返回的原始数据对象,和GraphQL Schema的对外定义无关。只要你查询Book时从数据库取出了author_id字段(哪怕GraphQL Schema不对外暴露),parent里就会包含这个值,就能在Book.author resolver里用它查询对应作者。
简单说:author_id是服务端内部使用的字段,不需要暴露给客户端,自然不用加到GraphQL Schema里。
问题2:Query.book中如何解析Author字段?避免额外调用/无限循环?
关于无限循环:
完全不用担心。GraphQL的Resolver是按需触发的:只有当客户端的查询明确请求了author字段时,Book.author resolver才会执行;而且Resolver只会从父字段到子字段单向执行,不会出现反向循环的情况。
避免额外数据库调用的两种方案:
方案1:关联查询(一次性拉取所有需要的数据)
如果你的数据库支持关联查询(比如SQL的JOIN),可以在Query.book里直接查询Book和关联的Author数据,整理成GraphQL需要的结构返回,这样Book.author resolver可以直接返回预加载的数据,不用再查数据库:
Query: { book: async (parent, args, context, info) => { // 伪代码:用JOIN关联查询书籍和作者数据 const bookData = await db.books .join('authors', 'books.author_id', 'authors.id') .select('books.title', 'books.author_id', 'authors.name') .where({ 'books.id': args.id }) .first(); return { title: bookData.title, author_id: bookData.author_id, // 保留给内部Resolver使用 author: { name: bookData.name } }; } } // 简化Book.author resolver Book: { author: (parent) => parent.author // 直接返回预加载的作者数据 }
缺点:如果客户端的查询没请求author字段,JOIN查询会浪费资源。
方案2:用DataLoader解决N+1问题(按需加载+批量查询)
如果客户端查询可能包含或不包含author字段,更灵活的方式是保留按需查询,同时用DataLoader批量处理多次请求,避免N+1问题(比如查询100本书时,不会发100次查作者的请求,而是合并成1次)。
- 创建DataLoader实例:
import DataLoader from 'dataloader'; // 创建作者批量加载器,自动合并重复查询 const authorLoader = new DataLoader(async (authorIds) => { const authors = await db.author.select({ where: { id: authorIds } }); // 按传入的id顺序返回结果,保证对应关系 return authorIds.map(id => authors.find(author => author.id === id)); });
- 在Apollo Server的context中传入loader:
const server = new ApolloServer({ typeDefs, resolvers, context: () => ({ authorLoader }) });
- 修改
Book.authorresolver使用loader:
Book: { author: async (parent, args, context) => { // 用loader批量查询,自动去重合并请求 return context.authorLoader.load(parent.author_id); } }
这样既实现了按需加载,又避免了重复的数据库请求。
同问题2:Query.author中如何解析[Book]字段?
逻辑和上面完全一致,两种方案可选:
方案1:关联查询预加载书籍
Query: { author: async (parent, args, context, info) => { // 伪代码:关联查询作者和其所有书籍 const authorData = await db.authors .leftJoin('books', 'authors.id', 'books.author_id') .select('authors.name', 'books.title', 'books.author_id') .where({ 'authors.id': args.id }) .first(); // 整理书籍列表数据 const books = authorData.books.map(book => ({ title: book.title, author_id: book.author_id })); return { name: authorData.name, books: books }; } } // 简化Author.books resolver Author: { books: (parent) => parent.books }
方案2:用DataLoader批量查询书籍
- 创建书籍的DataLoader:
const bookLoader = new DataLoader(async (authorIds) => { const books = await db.books.select({ where: { author_id: authorIds } }); // 按作者id分组返回对应书籍列表 return authorIds.map(id => books.filter(book => book.author_id === id)); });
- 传入context:
context: () => ({ authorLoader, bookLoader })
- 修改
Author.booksresolver:
Author: { books: async (parent, args, context) => { return context.bookLoader.load(parent.id); } }
内容的提问来源于stack exchange,提问作者47ndr

