首页/新闻资讯/正文详情

HowToGraphQL Scala 后端:用 Sangria Deferred Resolver 与 Fetcher 实现批量取数与查询去重

发布时间:2026/9/25 3:12:04 来源:云帆数科 栏目:资讯中心
HowToGraphQL Scala 后端:用 Sangria Deferred Resolver 与 Fetcher 实现批量取数与查询去重
【免费下载链接】howtographqlThe Fullstack Tutorial for GraphQL项目地址https://gitcode.com/gh_mirrors/ho/howtographql点击查看免费下载在 content/backend/graphql-scala 这套以 Scala Sangria 构建 GraphQL 服务器的教程中Deferred Resolver与Fetcher是解决 N1 查询、重复取数与缓存问题的核心机制。本文基于教程第 4 章4-deferred-resolvers.md展开讲解如何为Link实体定义 Fetcher、改造 Query 字段、将 Deferred Resolver 接入 Executor并完整解释HasIdtype class 的三种提供方式。读完本文你将掌握 Sangria 中批量取数的标准写法并能用一次调试断点直观验证同一 ID 只查一次数据库的去重效果。背景直接调用 DAO 的重复查询问题在进入 Fetcher 之前先回顾上一章3-handling-arguments.md埋下的伏笔。当时link与links两个字段的 resolver 直接调用 DAOField(link, OptionType(LinkType), arguments List(Argument(id, IntType)), resolve c c.ctx.dao.getLink(c.argInt) )如果你执行下面这个查询同一个id出现两次query { l1: link(id: 1) { id url } l2: link(id: 1) { id url } }并给DAO.getLink打断点会发现它对同一个id被调用了两次——因为resolve函数直接调用 DAO每解析一个字段就触发一次查询。这正是 N1 与重复查询问题的典型场景客户端一次请求可能触发数据库多次往返。Sangria 为此提供了Deferred Resolver机制它在真正执行查询前先收集需求、合并同类项从而优化或缓存查询。Fetcher 与 Deferred Resolver 的关系Sangria 中批量取数有两层 APIDeferred Resolver底层、高效的 API。它给你最大的控制权但使用起来更复杂、也更不安全需要你自行处理更多细节。FetcherDeferred Resolver的特化specialized版本提供高层 API通常已能满足绝大部分需求。它会基于 ID 或关系优化实体解析并且内置**去重deduplication与结果缓存caching**能力还能处理更多细节如关联解析。简单来说Fetcher 是 Deferred Resolver 的友好包装你日常优先用 Fetcher 即可。这也是本教程主线采用的方案。为 Link 实体定义 Fetcher在GraphQLSchema.scala中先补上导入再在QueryType声明之前定义一个linksFetcher//in import section: import sangria.execution.deferred.Fetcher //in the body: val linksFetcher Fetcher( (ctx: MyContext, ids: Seq[Int]) ctx.dao.getLinks(ids) )这里依然使用DAO.getLinks从数据库取数据getLinks是上一章在DAO.scala中定义的批量化查询函数内部实现为db.run(Links.filter(_.id inSet ids).result)但现在它被包进了Fetcher。关键区别在于Fetcher 会在调用之前优化查询——先收集本次执行中所有需要获取的数据再一次性执行数据库查询。缓存与去重机制则避免发出重复查询让响应更快。改造 QueryType 字段以使用 Fetcher接下来把link与links两个字段的 resolver 换成 Fetcher 提供的延迟方法Field(link, OptionType(LinkType), arguments Id :: Nil, resolve c linksFetcher.deferOpt(c.arg(Id)) ), Field(links, ListType(LinkType), arguments List(Argument(ids, ListInputType(IntType))), resolve c linksFetcher.deferSeq(c.arg(Ids)) )注意两个字段共用同一个 fetcher但使用方式不同deferOpt传入单个ID期望返回可空对象对应OptionType(LinkType)即link字段。deferSeq传入ID 列表期望返回对象序列对应ListType(LinkType)即links字段。Id与Ids是教程中提取出的参数常量DRY 写法定义如下val Id Argument(id, IntType) val Ids Argument(ids, ListInputType(IntType))将 Deferred Resolver 注入 Executor定义了 resolver 之后还必须让执行器知道它的存在。第一步是在GraphQLSchema中用DeferredResolver.fetchers把 fetcher 包装成 resolver 常量//add to imports: import sangria.execution.deferred.DeferredResolver //add after linksFetcher: val Resolver DeferredResolver.fetchers(linksFetcher)第二步把该 resolver 传入Executor.execute。打开GraphQLServer.scala修改executeGraphQLQuery函数Executor.execute( GraphQLSchema.SchemaDefinition, query, MyContext(dao), variables vars, operationName operation, deferredResolver GraphQLSchema.Resolver ) //the rest without changesdeferredResolver参数是 SangriaExecutor的可选参数专门用于注册批量取数逻辑不传它Fetcher 便不会被激活。执行器在执行查询时会先让 Deferred Resolver 汇总所有 deferred 请求再按 fetcher 分批发送给 DAO。清理删除不再需要的 getLink由于link字段现在通过linksFetcher.deferOpt复用DAO.getLinks单元素也是列表的特例getLink单数版函数就冗余了打开DAO类删除getLink函数。删除后DAO 中与 Link 相关的取数逻辑统一收敛到getLinks(ids: Seq[Int])这也减少了代码分支符合单一批量入口的设计。HasId type classFetcher 如何从实体提取 ID如果此时直接执行查询你会得到一个编译/运行错误。原因是Fetcher需要知道如何从实体中提取 ID这个机制就是HasIdtype class。它有三种提供方式方式一显式传入val linksFetcher Fetcher( (ctx: MyContext, ids: Seq[Int]) ctx.dao.getLinks(ids) )(HasId(_.id))方式二在当前作用域声明隐式常量本教程采用的路径//add to imports section: import sangria.execution.deferred.HasId //add just below LinkType declaration implicit val linkHasId HasIdLink, Int方式三放在模型伴生对象中隐式提供object Link { implicit val hasId HasIdLink, Int }方式三的好处是只要Link类型在作用域内HasId就自动可用无需额外导入。教程此处选择方式二但在后续的 Interfaces 章节6-interfaces.md中会演进为更统一的方案——定义Identifiabletrait 及伴生对象trait Identifiable { val id: Int } object Identifiable { implicit def hasId[T : Identifiable]: HasId[T, Int] HasId(_.id) }届时linkHasId、userHasId、voteHasId三个重复的隐式常量将被这一个泛型隐式方法取代新增实体零成本获得HasId。这一点印证了 Fetcher 的设计是可组合、可扩展的每新增一个实体如User、Vote只需照葫芦画瓢定义xxxFetcher再把它追加进DeferredResolver.fetchers(linksFetcher, usersFetcher, votesFetcher)的列表即可。完整的 GraphQLSchema 状态完成本章全部修改后GraphQLSchema.scala应如下所示这是教程给出的参考终态package com.howtographql.scala.sangria import sangria.schema.{ListType, ObjectType} import models._ import sangria.execution.deferred.{DeferredResolver, Fetcher, HasId} import sangria.schema._ import sangria.macros.derive._ object GraphQLSchema { implicit val LinkType deriveObjectType[Unit, Link]() implicit val linkHasId HasIdLink, Int val linksFetcher Fetcher( (ctx: MyContext, ids: Seq[Int]) ctx.dao.getLinks(ids) ) val Resolver DeferredResolver.fetchers(linksFetcher) val Id Argument(id, IntType) val Ids Argument(ids, ListInputType(IntType)) val QueryType ObjectType( Query, fieldsMyContext, Unit, resolve c c.ctx.dao.allLinks), Field(link, OptionType(LinkType), arguments Id :: Nil, resolve c linksFetcher.deferOpt(c.arg(Id)) ), Field(links, ListType(LinkType), arguments Ids :: Nil, resolve c linksFetcher.deferSeq(c.arg(Ids)) ) ) ) val SchemaDefinition Schema(QueryType) }注意几点LinkType使用deriveObjectType宏由 case class 自动派生省去手写字段映射样板代码linkHasId是隐式值Fetcher构造时自动拾取Resolver常量供GraphQLServer.scala中的Executor.execute使用SchemaDefinition保持不变schema 结构对客户端无感知差异优化发生在执行层。实测验证同一 ID 只查询一次用任意方式给DAO.getLinks打断点例如 IDE 断点或临时加日志然后执行下面的查询query { l1: link(id: 1){ id url } l2: link(id: 1){ id url } links(ids: [2,3]){ id url } }尽管查询里id: 1被请求了两次l1与l2两个别名调试时你会看到getLinks对 ID1只被调用了一次。这就是 Fetcher 的去重与缓存机制在起作用它先收集整个查询中所有 deferred 请求合并重复 ID再批量执行。同理links(ids: [2,3])也会与前面的请求合并处理整个查询最多只需一次数据库往返。对比上一章直接调用 DAO 的实现同一个查询会导致getLink对 ID 1 调用两次——这是本机制最直观的价值证明。延伸把该模式推广到其他实体在后续章节中同样模式被复制到User与Vote参见 6-interfaces.md先在DAO中实现getUsers(ids)、getVotes(ids)批量函数再在GraphQLSchema中定义对应的 fetcher 与HasId最后把新 fetcher 追加进Resolverval usersFetcher Fetcher( (ctx: MyContext, ids: Seq[Int]) ctx.dao.getUsers(ids) ) val votesFetcher Fetcher( (ctx: MyContext, ids: Seq[Int]) ctx.dao.getVotes(ids) ) val Resolver DeferredResolver.fetchers(linksFetcher, usersFetcher, votesFetcher)QueryType.fields中相应地新增Field(users, ListType(UserType), arguments List(Ids), resolve c usersFetcher.deferSeq(c.arg(Ids)) )这说明 Fetcher 是教程整套后端Query/Mutation/认证通用的基础设施只要遵守DAO 提供批量取数函数 Schema 定义 fetcher Resolver 注册三步任何实体都能获得批量与去重能力。小结与下一步本章的核心收获可概括为三点Fetcher是Deferred Resolver的高层特化自带去重、缓存与关联优化resolver 应返回deferOpt/deferSeq延迟请求并在Executor.execute中通过deferredResolver GraphQLSchema.Resolver激活HasIdtype class 负责从实体提取 ID可用显式参数、作用域隐式常量或伴生对象三种方式提供后续可收敛为基于Identifiabletrait 的单一泛型实现。下一章将为Link类增加createdAt字段记录链接添加时刻。H2 与 Sangria 都不原生支持该类型届时需要自定义 Scalar 类型来完成序列化与解析这正是 5-custom-scalars.md 要解决的问题。赞分享【免费下载链接】howtographqlThe Fullstack Tutorial for GraphQL项目地址https://gitcode.com/gh_mirrors/ho/howtographql点击查看免费下载相关推荐GraphQL Scala 与 Sangria 实战用 Relation 与 Fetcher 打通 User、Link、Vote 模型关联查询GraphQL Scala 与 Sangria 实战用 Relation 与 Fetcher 打通 User、Link、Vote 模型关联查询 本文基于 HoHowToGraphQL Scala 教程用 Sangria 接口Interfaces抽象 User 与 Vote 实体HowToGraphQL Scala 教程用 Sangria 接口Interfaces抽象 User 与 Vote 实体 本篇文章以 HowToGraphSangria: Scala GraphQL 实现Sangria: Scala GraphQL 实现 项目基础介绍和主要编程语言 Sangria 是一个基于 Scala 编程语言的 GraphQL 实现。它旨在上一篇容器化应用依赖隔离案例gh_mirrors/do/dockerfiles Go mod实战下一篇Agent-Native自动化测试智能体如何测试自己的代码创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关推荐

如何设计一套有趣又有料的知识点选择题:选题、出题与解析全拆解
如何设计一套有趣又有料的知识点选择题:选题、出题与解析全拆解

1. 为什么我会沉迷于整理“乱七八糟”的知识点选择题说实话,我最早开始整理这套题,动机特别简单——被家里的小朋友问住了。有天晚饭后,他指着电视里正在重播的《西游记》问我:“孙悟空被压了五百年,那唐太宗当皇帝的时… · 2026/9/25 3:11:58

活动海报PSD源文件全攻略:选稿、改稿与交付避坑指南
活动海报PSD源文件全攻略:选稿、改稿与交付避坑指南

简介:三十款广告海报与活动物料PSD源文件合集,覆盖餐饮美食、母婴零售、旅游交通、花艺园艺、化妆美容、新能源、物流运输、节日庆典等众多领域,适合电商运营、平面设计师、门店企划及接单自由职业者按需选用。模板类型非常丰富,既… · 2026/9/25 3:11:58

源师兄活动信息平台『源师兄工坊』揭秘:青少年编程、物联网与AI实践课程如何玩转声控灯和游戏项目
源师兄活动信息平台『源师兄工坊』揭秘:青少年编程、物联网与AI实践课程如何玩转声控灯和游戏项目

源师兄活动信息平台『源师兄工坊』揭秘:青少年编程、物联网与AI实践课程如何玩转声控灯和游戏项目 【免费下载链接】活动 本仓库由“源师兄”团队维护,旨在集中发布和管理各类活动信息,包括科技赛事、文化节、创新漂流瓶、行业展会等。为参与… · 2026/9/25 3:11:58

Kubebuilder 子模块布局(Sub-Module Layouts):为 API 与 Controller 拆分独立 go.mod 的完整实战指南
Kubebuilder 子模块布局(Sub-Module Layouts):为 API 与 Controller 拆分独立 go.mod 的完整实战指南

开发者工具代码生成CLI云原生后端 【免费下载链接】kubebuilder Kubebuilder - SDK for building Kubernetes APIs using CRDs 项目地址: https://gitcode.com/gh_mirrors/ku/kubebuilder 点击查看 免费下载 导读 Kubebuilder 脚手架默认把 API 类型与 Controller… · 2026/9/25 3:42:07

AI造AI传闻背后:从GPU算子到Agent自动化的RSI技术真相
AI造AI传闻背后:从GPU算子到Agent自动化的RSI技术真相

1. 从"AI造AI"传闻说起:这条消息到底在讲什么最近圈子里传得最凶的一条消息,大概就是"OpenAI内部曝光AI开始自己造AI,奥特曼急发全球暂停令"。我第一眼看到这个标题的时候,反应不是震惊,而是先把它… · 2026/9/25 3:42:07

Twig keys 过滤器详解:从模板语法到 CoreExtension 源码实现
Twig keys 过滤器详解:从模板语法到 CoreExtension 源码实现

后端 【免费下载链接】Twig Twig, the flexible, fast, and secure template language for PHP 项目地址: https://gitcode.com/gh_mirrors/tw/Twig 点击查看 免费下载 本篇指南围绕 Twig 模板语言中的 keys 过滤器展开:它如何从序列(sequen… · 2026/9/25 3:42:07

Moto 架构深度解析:装饰器拦截机制、请求路由与文件组织
Moto 架构深度解析:装饰器拦截机制、请求路由与文件组织

Mock测试 【免费下载链接】moto A library that allows you to easily mock out tests based on AWS infrastructure. 项目地址: https://gitcode.com/gh_mirrors/mo/moto 点击查看 免费下载 本篇技术指南以 Moto 官方贡献者文档《Architecture》为骨架&#xff0c… · 2026/9/25 3:42:07

html-anything 团队 OKR 追踪 Skill 模板深度解析:从 SKILL.md 规格到 Dashboard 落地实现
html-anything 团队 OKR 追踪 Skill 模板深度解析:从 SKILL.md 规格到 Dashboard 落地实现

AI 应用人工智能AI AgentAI 写作媒体生成 【免费下载链接】html-anything ✨ The agentic HTML editor — your local AI agent writes the HTML, you ship it. 🚀 75 Skills 9 Surfaces (magazine deck poster XHS / tweet prototype data report Hyperfram… · 2026/9/25 3:42:01

生产级知识库与Agent网关优化实践:稳定、可控、可观测
生产级知识库与Agent网关优化实践:稳定、可控、可观测

1. 先聊聊“生产级”这三个字的分量不知道你有没有这种经历:本地写一个知识库 Demo,文档全部切好、索引建好、问什么答什么,效果棒得想立刻上线。结果真到了线上,几十个用户一进来,延迟飙到十几秒,回答一会… · 2026/9/25 3:42:01

数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)
数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码