如何在NestJS GraphQL中复用@ResolveField结果,避免重复调用服务
优化GraphQL OrderResolver,避免重复调用用户查询服务
我有如下OrderResolver代码,当前users和usersCount两个字段的解析需要两次调用usersService.findAllByOrderId,因为usersCount里无法直接使用order.users(此时该字段还未被解析),想优化查询避免重复调用:
@Resolver(() => OrderEntity) export class OrderResolver { constructor( private readonly orderService: OrderService, private readonly usersService: UsersService, ) {} // 其他 resolver 方法... @ResolveField('users', () => [UsersEntity]) async users(@Parent() order: OrderEntity): Promise<UserEntity[]> { return await this.usersService.findAllByOrderId(order.id); } @ResolveField('usersCount', () => Int) async usersCount(@Parent() order: OrderEntity): Promise<number> { const users = await this.usersService.findAllByOrderId(order.id); return users.length; // 无法直接用 order.users.length,因为此时该字段未定义 } }
方案一:解析users时将数据挂载到order对象,供usersCount复用
可以在users字段的解析方法里,把查询到的用户数据挂载到order对象的一个临时属性上(比如_users),之后usersCount直接读取这个临时属性即可,无需重复查询:
@Resolver(() => OrderEntity) export class OrderResolver { constructor( private readonly orderService: OrderService, private readonly usersService: UsersService, ) {} // 其他 resolver 方法... @ResolveField('users', () => [UsersEntity]) async users(@Parent() order: OrderEntity & { _users?: UserEntity[] }): Promise<UserEntity[]> { const users = await this.usersService.findAllByOrderId(order.id); // 将查询结果挂载到临时属性 order._users = users; return users; } @ResolveField('usersCount', () => Int) async usersCount(@Parent() order: OrderEntity & { _users?: UserEntity[] }): Promise<number> { // 优先使用已缓存的临时数据,没有的话再查询(兼容只请求usersCount的场景) const users = order._users ?? await this.usersService.findAllByOrderId(order.id); return users.length; } }
这种方式简单直接,适合单 resolver 内的复用场景,但要注意临时属性的命名避免和实体原有字段冲突。
方案二:使用DataLoader实现批量查询与缓存(更推荐)
如果你的项目中存在多处类似的重复查询场景,使用DataLoader是更优雅的方案,它能自动批量处理查询并缓存结果,避免重复请求:
- 首先创建一个UserDataLoader:
import DataLoader from 'dataloader'; @Injectable() export class UserDataLoader { constructor(private readonly usersService: UsersService) {} createFindAllByOrderIdLoader() { return new DataLoader<number, UserEntity[]>(async (orderIds) => { // 批量查询所有订单对应的用户 const usersByOrder = await this.usersService.findAllByOrderIds(orderIds); // 按orderId分组并返回对应结果 return orderIds.map(orderId => usersByOrder.filter(u => u.orderId === orderId)); }); } }
注意:需要在usersService中实现findAllByOrderIds方法,支持批量查询多个订单的用户,提升查询效率。
- 在OrderResolver中注入并使用这个DataLoader:
@Resolver(() => OrderEntity) export class OrderResolver { constructor( private readonly orderService: OrderService, private readonly userDataLoader: UserDataLoader, ) {} // 创建DataLoader实例(每个请求创建一个,避免跨请求缓存) @Loader() usersByOrderIdLoader() { return this.userDataLoader.createFindAllByOrderIdLoader(); } @ResolveField('users', () => [UsersEntity]) async users( @Parent() order: OrderEntity, @Loader(usersByOrderIdLoader) loader: DataLoader<number, UserEntity[]> ): Promise<UserEntity[]> { return loader.load(order.id); } @ResolveField('usersCount', () => Int) async usersCount( @Parent() order: OrderEntity, @Loader(usersByOrderIdLoader) loader: DataLoader<number, UserEntity[]> ): Promise<number> { const users = await loader.load(order.id); return users.length; } }
使用DataLoader不仅能避免重复查询,还能将多个单独的查询合并成一个批量查询,大幅提升性能,尤其在一次请求中返回多个Order的场景下效果更明显。
内容的提问来源于stack exchange,提问作者buryakivskiy
相关产品推荐
相关产品推荐

