请求执行
ExecutionGraphQlService
是调用 GraphQL Java 执行请求的主要 Spring 抽象。底层传输,例如 HTTP,委托给 ExecutionGraphQlService
来处理请求。
主要实现 DefaultExecutionGraphQlService
配置了一个 GraphQlSource
,用于访问要调用的 graphql.GraphQL
实例。
GraphQLSource
GraphQlSource
是一个契约,用于公开要使用的 graphql.GraphQL
实例,其中还包括一个构建该实例的构建器 API。默认构建器可以通过 GraphQlSource.schemaResourceBuilder()
获取。
Boot 启动器 创建此构建器的实例,并进一步初始化它以从可配置位置 加载模式文件,公开属性 以应用于 GraphQlSource.Builder
,检测 RuntimeWiringConfigurer
bean、用于 GraphQL 指标 的 Instrumentation bean,以及用于 异常解析 的 DataFetcherExceptionResolver
和 SubscriptionExceptionResolver
bean。对于进一步的自定义,您还可以声明一个 GraphQlSourceBuilderCustomizer
bean,例如
import org.springframework.boot.autoconfigure.graphql.GraphQlSourceBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class GraphQlConfig {
@Bean
public GraphQlSourceBuilderCustomizer sourceBuilderCustomizer() {
return (builder) ->
builder.configureGraphQl((graphQlBuilder) ->
graphQlBuilder.executionIdProvider(new CustomExecutionIdProvider()));
}
}
模式资源
GraphQlSource.Builder
可以配置一个或多个 Resource
实例以进行解析并合并在一起。这意味着模式文件几乎可以从任何位置加载。
默认情况下,Boot 启动器 查找扩展名为“.graphqls”或“.gqls”的模式文件,位于 classpath:graphql/**
下,通常是 src/main/resources/graphql
。您还可以使用文件系统位置,或 Spring Resource
层次结构支持的任何位置,包括从远程位置、存储或内存加载模式文件的自定义实现。
使用 classpath*:graphql/**/ 在多个类路径位置(例如,跨多个模块)查找模式文件。 |
模式创建
默认情况下,GraphQlSource.Builder
使用 GraphQL Java SchemaGenerator
创建 graphql.schema.GraphQLSchema
。这适用于典型用例,但如果您需要使用不同的生成器,则可以注册 schemaFactory
回调
GraphQlSource.Builder builder = ...
builder.schemaResources(..)
.configureRuntimeWiring(..)
.schemaFactory((typeDefinitionRegistry, runtimeWiring) -> {
// create GraphQLSchema
})
有关如何在 Spring Boot 中配置此项,请参阅 GraphQLSource 部分。
如果您对联合体感兴趣,请参阅 联合体 部分。
RuntimeWiringConfigurer
RuntimeWiringConfigurer
用于注册以下内容:
-
自定义标量类型。
-
处理 指令 的代码。
-
直接
DataFetcher
注册。 -
等等…
Spring 应用通常不需要执行直接的 DataFetcher 注册。相反,控制器方法通过 AnnotatedControllerConfigurer (它是一个 RuntimeWiringConfigurer )注册为 DataFetcher 。 |
GraphQL Java 服务器应用仅使用 Jackson 将数据映射进行序列化和反序列化。客户端输入被解析为映射。服务器输出根据字段选择集组装成映射。这意味着您不能依赖 Jackson 序列化/反序列化注解。相反,您可以使用 自定义标量类型。 |
Boot 启动器 检测类型为 RuntimeWiringConfigurer
的 bean,并在 GraphQlSource.Builder
中注册它们。这意味着在大多数情况下,您的配置中会有如下内容:
@Configuration
public class GraphQlConfig {
@Bean
public RuntimeWiringConfigurer runtimeWiringConfigurer(BookRepository repository) {
GraphQLScalarType scalarType = ... ;
SchemaDirectiveWiring directiveWiring = ... ;
return wiringBuilder -> wiringBuilder
.scalar(scalarType)
.directiveWiring(directiveWiring);
}
}
如果您需要添加 WiringFactory
(例如,进行考虑模式定义的注册),请实现接受 RuntimeWiring.Builder
和输出 List<WiringFactory>
的备用 configure
方法。这使您可以添加任意数量的工厂,然后按顺序调用它们。
TypeResolver
GraphQlSource.Builder
将 ClassNameTypeResolver
注册为默认的 TypeResolver
,用于尚未通过 RuntimeWiringConfigurer
进行此类注册的 GraphQL 接口和联合体。GraphQL Java 中 TypeResolver
的目的是确定 GraphQL 对象类型,该类型是从 GraphQL 接口或联合体字段的 DataFetcher
返回的值。
ClassNameTypeResolver
尝试将值的简单类名与 GraphQL 对象类型匹配,如果匹配不成功,它还会遍历其超类型(包括基类和接口),查找匹配项。ClassNameTypeResolver
提供了一个选项来配置名称提取函数以及 Class
到 GraphQL 对象类型名称的映射,这应该有助于涵盖更多特殊情况
GraphQlSource.Builder builder = ...
ClassNameTypeResolver classNameTypeResolver = new ClassNameTypeResolver();
classNameTypeResolver.setClassNameExtractor((klass) -> {
// Implement Custom ClassName Extractor here
});
builder.defaultTypeResolver(classNameTypeResolver);
有关如何在 Spring Boot 中配置此项,请参阅 GraphQLSource 部分。
指令
GraphQL 语言支持指令,这些指令“描述 GraphQL 文档中的替代运行时执行和类型验证行为”。指令类似于 Java 中的注解,但在 GraphQL 文档中的类型、字段、片段和操作上声明。
GraphQL Java 提供了 SchemaDirectiveWiring
契约来帮助应用检测和处理指令。有关更多详细信息,请参阅 GraphQL Java 文档中的 模式指令。
在 Spring GraphQL 中,您可以通过 RuntimeWiringConfigurer
注册 SchemaDirectiveWiring
。 Boot 启动器 会检测此类 bean,因此您可能会有如下内容:
@Configuration
public class GraphQlConfig {
@Bean
public RuntimeWiringConfigurer runtimeWiringConfigurer() {
return builder -> builder.directiveWiring(new MySchemaDirectiveWiring());
}
}
有关指令支持的示例,请查看 GraphQL Java 的扩展验证 库。 |
ExecutionStrategy
GraphQL Java 中的 ExecutionStrategy
驱动请求字段的获取。要创建一个 ExecutionStrategy
,需要提供一个 DataFetcherExceptionHandler
。默认情况下,Spring for GraphQL 会创建异常处理程序(如 异常 中所述)并将其设置在 GraphQL.Builder
上。然后,GraphQL Java 使用它来创建配置了异常处理程序的 AsyncExecutionStrategy
实例。
如果需要创建自定义的 ExecutionStrategy
,可以检测 DataFetcherExceptionResolver
并以相同的方式创建异常处理程序,并使用它来创建自定义的 ExecutionStrategy
。例如,在 Spring Boot 应用程序中
@Bean
GraphQlSourceBuilderCustomizer sourceBuilderCustomizer(
ObjectProvider<DataFetcherExceptionResolver> resolvers) {
DataFetcherExceptionHandler exceptionHandler =
DataFetcherExceptionResolver.createExceptionHandler(resolvers.stream().toList());
AsyncExecutionStrategy strategy = new CustomAsyncExecutionStrategy(exceptionHandler);
return sourceBuilder -> sourceBuilder.configureGraphQl(builder ->
builder.queryExecutionStrategy(strategy).mutationExecutionStrategy(strategy));
}
模式转换
如果希望在模式创建后遍历并转换模式,并对模式进行更改,可以通过 builder.schemaResources(..).typeVisitorsToTransformSchema(..)
注册一个 graphql.schema.GraphQLTypeVisitor
。请记住,这比 模式遍历 更昂贵,因此通常优先选择遍历而不是转换,除非需要进行模式更改。
模式遍历
如果希望在模式创建后遍历模式,并可能对 GraphQLCodeRegistry
应用更改,可以通过 builder.schemaResources(..).typeVisitors(..)
注册一个 graphql.schema.GraphQLTypeVisitor
。但是,请记住,此类访问者无法更改模式。如果需要对模式进行更改,请参阅 模式转换。
模式映射检查
如果查询、变异或订阅操作没有 DataFetcher
,则不会返回任何数据,也不会执行任何有用的操作。同样,如果模式类型的字段既没有通过 DataFetcher
注册明确覆盖,也没有通过查找匹配的 Class
属性的默认 PropertyDataFetcher
隐式覆盖,则始终为 null
。
GraphQL Java 不会执行检查以确保每个模式字段都被覆盖,并且作为一个较低级别的库,GraphQL Java 根本不知道 DataFetcher
可以返回什么或它依赖于哪些参数,因此无法执行此类验证。这可能导致间隙,具体取决于测试覆盖率,在运行时可能不会被发现,直到客户端遇到“静默”的 null
值或非空字段错误。
Spring for GraphQL 中的 SelfDescribingDataFetcher
接口允许 DataFetcher
公开信息,例如返回类型和预期参数。所有针对 控制器方法、Querydsl 和 Query by Example 的内置 Spring DataFetcher
实现都是此接口的实现。对于带注释的控制器,返回类型和预期参数基于控制器方法签名。这使得可以在启动时检查模式映射以确保以下内容
-
模式字段要么具有
DataFetcher
注册,要么具有相应的Class
属性。 -
DataFetcher
注册引用存在的模式字段。 -
DataFetcher
参数具有匹配的模式字段参数。
要启用模式检查,请按如下所示自定义 GraphQlSource.Builder
。在本例中,报告只是记录在日志中,但您可以选择采取任何操作
GraphQlSource.Builder builder = ...
builder.schemaResources(..)
.inspectSchemaMappings(report -> {
logger.debug(report);
});
示例报告
GraphQL schema inspection: Unmapped fields: {Book=[title], Author[firstName, lastName]} (1) Unmapped registrations: {Book.reviews=BookController#reviews[1 args]} (2) Unmapped arguments: {BookController#bookSearch[1 args]=[myAuthor]} (3) Skipped types: [BookOrAuthor] (4)
1 | 未以任何方式覆盖的模式字段 |
2 | 指向不存在的字段的 DataFetcher 注册 |
3 | 不存在的 DataFetcher 预期参数 |
4 | 已跳过的模式类型(接下来解释) |
在某些情况下,模式类型的 Class
类型未知。可能是 DataFetcher
未实现 SelfDescribingDataFetcher
,或者声明的返回类型过于通用(例如 Object
)或未知(例如 List<?>
),或者可能缺少 DataFetcher
。在这种情况下,模式类型被列为已跳过,因为它无法验证。对于每个跳过的类型,DEBUG 消息都会解释跳过的原因。
联合和接口
对于联合,检查会遍历成员类型并尝试查找相应的类。对于接口,检查会遍历实现类型并查找相应的类。
默认情况下,可以在以下情况下开箱即用地检测相应的 Java 类
-
Class
的简单名称与 GraphQL 联合成员或接口实现类型名称匹配,并且Class
位于与映射到联合或接口字段的控制器方法或控制器类的返回类型相同的包中。 -
在模式的其他部分检查
Class
,其中映射的字段是具体联合成员或接口实现类型。 -
您已注册了一个 TypeResolver,它具有显式的
Class
到 GraphQL 类型映射。
如果上述方法均无帮助,并且在模式检查报告中 GraphQL 类型被报告为已跳过,则可以进行以下自定义
-
显式地将 GraphQL 类型名称映射到 Java 类或多个类。
-
配置一个函数来自定义如何将 GraphQL 类型名称适配到简单的
Class
名称。这可以帮助处理特定的 Java 类命名约定。 -
提供一个
ClassNameTypeResolver
来将 GraphQL 类型映射到 Java 类。
例如
GraphQlSource.Builder builder = ...
builder.schemaResources(..)
.inspectSchemaMappings(
initializer -> initializer.classMapping("Author", Author.class)
logger::debug);
操作缓存
GraphQL Java 必须在执行操作之前解析和验证它。这可能会显着影响性能。为了避免重新解析和验证的需要,应用程序可以配置一个 PreparsedDocumentProvider
来缓存和重用 Document 实例。GraphQL Java 文档 提供了有关通过 PreparsedDocumentProvider
进行查询缓存的更多详细信息。
在 Spring GraphQL 中,您可以通过 GraphQlSource.Builder#configureGraphQl
注册 PreparsedDocumentProvider
:。
// Typically, accessed through Spring Boot's GraphQlSourceBuilderCustomizer
GraphQlSource.Builder builder = ...
// Create provider
PreparsedDocumentProvider provider =
new ApolloPersistedQuerySupport(new InMemoryPersistedQueryCache(Collections.emptyMap()));
builder.schemaResources(..)
.configureRuntimeWiring(..)
.configureGraphQl(graphQLBuilder -> graphQLBuilder.preparsedDocumentProvider(provider))
有关如何在 Spring Boot 中配置此项,请参阅 GraphQLSource 部分。
线程模型
大多数 GraphQL 请求都受益于在获取嵌套字段时的并发执行。这就是如今大多数应用程序依赖 GraphQL Java 的 AsyncExecutionStrategy
的原因,它允许数据获取器返回 CompletionStage
并并发执行而不是串行执行。
Java 21 和虚拟线程增加了有效使用更多线程的重要能力,但为了使请求执行更快地完成,仍然需要并发执行而不是串行执行。
Spring for GraphQL 支持
-
反应式数据获取器,这些获取器将被适配到
AsyncExecutionStrategy
预期的CompletionStage
。 -
CompletionStage
作为返回值。 -
Kotlin 协程方法作为控制器方法。
-
@SchemaMapping 和 @BatchMapping 方法可以返回
Callable
,该Callable
将提交到Executor
(例如 Spring FrameworkVirtualThreadTaskExecutor
)。要启用此功能,必须在AnnotatedControllerConfigurer
上配置Executor
。
Spring for GraphQL 在 Spring MVC 或 WebFlux 上运行作为传输。Spring MVC 使用异步请求执行,除非生成的 CompletableFuture
在 GraphQL Java 引擎返回后立即完成,如果请求足够简单并且不需要异步数据获取,就会出现这种情况。
反应式 DataFetcher
默认的 GraphQlSource
生成器启用了对 DataFetcher
返回 Mono
或 Flux
的支持,这些将适配到 CompletableFuture
,其中 Flux
值被聚合并转换为列表,除非请求是 GraphQL 订阅请求,在这种情况下,返回值保持为反应式流 Publisher
以流式传输 GraphQL 响应。
反应式 DataFetcher
可以依赖于对从传输层传播的 Reactor 上下文的访问,例如来自 WebFlux 请求处理,请参阅 WebFlux 上下文。
在订阅请求的情况下,GraphQL Java 将在项目可用且已获取其所有请求的字段后立即生成项目。由于这涉及异步数据获取的多个层,因此项目可能会以与其原始顺序不同的顺序通过网络发送。如果您希望 GraphQL Java 缓冲项目并保留原始顺序,可以通过在 GraphQLContext
中设置 SubscriptionExecutionStrategy.KEEP_SUBSCRIPTION_EVENTS_ORDERED
配置标志来实现。例如,可以使用自定义的 Instrumentation
来实现这一点
import graphql.ExecutionResult;
import graphql.execution.SubscriptionExecutionStrategy;
import graphql.execution.instrumentation.InstrumentationContext;
import graphql.execution.instrumentation.InstrumentationState;
import graphql.execution.instrumentation.SimpleInstrumentationContext;
import graphql.execution.instrumentation.SimplePerformantInstrumentation;
import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class GraphQlConfig {
@Bean
public SubscriptionOrderInstrumentation subscriptionOrderInstrumentation() {
return new SubscriptionOrderInstrumentation();
}
static class SubscriptionOrderInstrumentation extends SimplePerformantInstrumentation {
@Override
public InstrumentationContext<ExecutionResult> beginExecution(InstrumentationExecutionParameters parameters,
InstrumentationState state) {
// Enable option for keeping subscription results in upstream order
parameters.getGraphQLContext().put(SubscriptionExecutionStrategy.KEEP_SUBSCRIPTION_EVENTS_ORDERED, true);
return SimpleInstrumentationContext.noOp();
}
}
}
上下文传播
Spring for GraphQL 提供支持,以透明地从 HTTP 传输通过 GraphQL Java 传播上下文,并传播到 DataFetcher
及其调用的其他组件。这包括来自 Spring MVC 请求处理线程的 ThreadLocal
上下文和来自 WebFlux 处理管道的 Reactor Context
。
WebMvc
DataFetcher
和 GraphQL Java 调用的其他组件可能并不总是与 Spring MVC 处理程序在同一线程上执行,例如,如果异步 WebGraphQlInterceptor
或 DataFetcher
切换到另一个线程。
Spring for GraphQL 支持将 ThreadLocal
值从 Servlet 容器线程传播到 DataFetcher
和 GraphQL Java 调用的其他组件执行的线程。为此,应用程序需要为感兴趣的 ThreadLocal
值实现 io.micrometer.context.ThreadLocalAccessor
public class RequestAttributesAccessor implements ThreadLocalAccessor<RequestAttributes> {
@Override
public Object key() {
return RequestAttributesAccessor.class.getName();
}
@Override
public RequestAttributes getValue() {
return RequestContextHolder.getRequestAttributes();
}
@Override
public void setValue(RequestAttributes attributes) {
RequestContextHolder.setRequestAttributes(attributes);
}
@Override
public void reset() {
RequestContextHolder.resetRequestAttributes();
}
}
您可以使用全局 ContextRegistry
实例(可通过 io.micrometer.context.ContextRegistry#getInstance()
访问)在启动时手动注册 ThreadLocalAccessor
。您也可以通过 java.util.ServiceLoader
机制自动注册它。
WebFlux
一个 反应式 DataFetcher
可以依赖于对源自 WebFlux 请求处理链的 Reactor 上下文的访问。这包括由 WebGraphQlInterceptor 组件添加的 Reactor 上下文。
异常
在 GraphQL Java 中,DataFetcherExceptionHandler
决定如何在响应的“错误”部分表示数据获取的异常。应用程序只能注册一个处理程序。
Spring for GraphQL 注册了一个 DataFetcherExceptionHandler
,它提供默认处理并启用 DataFetcherExceptionResolver
合同。应用程序可以通过 GraphQLSource
生成器注册任意数量的解析器,这些解析器按顺序排列,直到其中一个将 Exception
解析为 List<graphql.GraphQLError>
。Spring Boot 启动器会检测此类型的 Bean。
DataFetcherExceptionResolverAdapter
是一个方便的基类,具有受保护的方法 resolveToSingleError
和 resolveToMultipleErrors
。
带注释的控制器编程模型允许使用带注释的异常处理程序方法处理数据获取异常,这些方法具有灵活的方法签名,有关详细信息,请参阅 @GraphQlExceptionHandler
。
可以根据 GraphQL Java 的 graphql.ErrorClassification
或 Spring GraphQL 的 ErrorType
为 GraphQLError
分配类别,后者定义了以下内容
-
BAD_REQUEST
-
UNAUTHORIZED
-
FORBIDDEN
-
NOT_FOUND
-
INTERNAL_ERROR
如果异常仍然未解决,则默认情况下将其归类为 INTERNAL_ERROR
,并显示一条通用消息,其中包含类别名称和来自 DataFetchingEnvironment
的 executionId
。该消息有意不透明,以避免泄漏实现细节。应用程序可以使用 DataFetcherExceptionResolver
自定义错误详细信息。
未解决的异常将记录在 ERROR 级别,并附带 executionId
以关联发送到客户端的错误。已解决的异常将记录在 DEBUG 级别。
请求异常
GraphQL Java 引擎在解析请求时可能会遇到验证或其他错误,从而阻止请求执行。在这种情况下,响应包含一个值为 null
的 "data" 键和一个或多个请求级别的 "errors",这些错误是全局的,即没有字段路径。
DataFetcherExceptionResolver
无法处理此类全局错误,因为它们是在执行开始之前以及调用任何 DataFetcher
之前引发的。应用程序可以使用传输级别的拦截器来检查和转换 ExecutionResult
中的错误。请参阅 WebGraphQlInterceptor
下面的示例。
订阅异常
订阅请求的 Publisher
可能会以错误信号完成,在这种情况下,底层传输(例如 WebSocket)会发送一条最终的 "error" 类型消息,其中包含 GraphQL 错误列表。
DataFetcherExceptionResolver
无法解决来自订阅 Publisher
的错误,因为数据 DataFetcher
最初只创建 Publisher
。之后,传输会订阅 Publisher
,然后 Publisher
可能会以错误完成。
应用程序可以注册一个 SubscriptionExceptionResolver
,以便解决来自订阅 Publisher
的异常,并将其解析为发送到客户端的 GraphQL 错误。
分页
GraphQL 的 游标连接规范 定义了一种通过一次返回一部分项目来浏览大型结果集的方法,其中每个项目都与一个游标配对,客户端可以使用该游标在引用项目之前或之后请求更多项目。
该规范将此模式称为“连接”,名称以 ~Connection
结尾的模式类型是表示分页结果集的连接类型。所有连接类型都包含一个名为“edges”的字段,其中 ~Edge
类型包含实际项目、游标和一个名为“pageInfo”的字段,该字段指示前方和后方是否存在更多项目。
连接类型
连接类型需要样板定义,如果未显式声明,Spring for GraphQL 的 ConnectionTypeDefinitionConfigurer
可以在启动时透明地添加这些定义。这意味着您只需要以下内容,连接和边缘类型将为您添加。
Query {
books(first:Int, after:String, last:Int, before:String): BookConnection
}
type Book {
id: ID!
title: String!
}
规范定义的 first
和 after
参数用于正向分页,允许客户端在给定游标“之后”请求前 N 个项目。类似地,用于反向分页的参数 last
和 before
参数允许请求在给定游标“之前”的最后 N 个项目。
该规范不建议同时包含 first 和 last ,并且还说明了分页结果变得不明确。在 Spring for GraphQL 中,如果存在 first 或 after ,则 last 和 before 将被忽略。 |
要生成连接类型,请按如下方式配置 ConnectionTypeDefinitionConfigurer
GraphQlSource.schemaResourceBuilder()
.schemaResources(..)
.typeDefinitionConfigurer(new ConnectionTypeDefinitionConfigurer)
以上将添加以下类型定义
type BookConnection {
edges: [BookEdge]!
pageInfo: PageInfo!
}
type BookEdge {
node: Book!
cursor: String!
}
type PageInfo {
hasPreviousPage: Boolean!
hasNextPage: Boolean!
startCursor: String
endCursor: String
}
Boot Starter 默认注册 ConnectionTypeDefinitionConfigurer
。
ConnectionAdapter
除了模式中的 连接类型 之外,您还需要等效的 Java 类型。GraphQL Java 提供了这些类型,包括泛型 Connection
和 Edge
类型以及 PageInfo
。
您可以从控制器方法返回 Connection
,但它需要样板代码才能将您的底层数据分页机制适配到 Connection
,创建游标,添加 ~Edge
包装器并创建 PageInfo
。
Spring for GraphQL 定义了 ConnectionAdapter
合同,以将项目容器适配到 Connection
。适配器从 DataFetcher
装饰器调用,该装饰器又由 ConnectionFieldTypeVisitor
添加。您可以按如下方式配置它
ConnectionAdapter adapter = ... ;
GraphQLTypeVisitor visitor = ConnectionFieldTypeVisitor.create(List.of(adapter)) (1)
GraphQlSource.schemaResourceBuilder()
.schemaResources(..)
.typeDefinitionConfigurer(..)
.typeVisitors(List.of(visitor)) (2)
1 | 创建具有一个或多个 ConnectionAdapter 的类型访问器。 |
2 | 注册类型访问器。 |
对于 Spring Data 的 Window
和 Slice
,存在内置的 内置 ConnectionAdapter
。您也可以创建自己的自定义适配器。ConnectionAdapter
实现依赖于 CursorStrategy
来为返回的项目创建游标。相同的策略也用于支持包含分页输入的 Subrange
控制器方法参数。
CursorStrategy
CursorStrategy
是一个合同,用于编码和解码引用大型结果集中项目位置的字符串游标。游标可以基于索引或基于键集。
ConnectionAdapter
使用它来为返回的项目编码游标。带注解的控制器 方法、Querydsl 存储库和 按示例查询 存储库使用它来解码来自分页请求的游标并创建 Subrange
。
CursorEncoder
是一个相关的合同,它进一步编码和解码字符串游标,使其对客户端不透明。EncodingCursorStrategy
将 CursorStrategy
与 CursorEncoder
组合。您可以使用 Base64CursorEncoder
、NoOpEncoder
或创建自己的编码器。
对于 Spring Data ScrollPosition
,存在一个 内置 CursorStrategy
。Boot Starter 在存在 Spring Data 时使用 Base64Encoder
注册一个 CursorStrategy<ScrollPosition>
。
排序
没有标准的方法可以在 GraphQL 请求中提供排序信息。但是,分页依赖于稳定的排序顺序。您可以使用默认顺序,或者公开输入类型并从 GraphQL 参数中提取排序详细信息。
对于 Spring Data 的 Sort
作为控制器方法参数,存在 内置 支持。为此,您需要一个 SortStrategy
bean。
批量加载
给定一个 Book
及其 Author
,我们可以为书籍创建一个 DataFetcher
,为作者创建另一个 DataFetcher
。这允许选择有或没有作者的书籍,但这意味着书籍和作者不会一起加载,这在查询多本书时特别低效,因为每本书的作者都是单独加载的。这被称为 N+1 选择问题。
DataLoader
GraphQL Java 提供了一个 DataLoader
机制,用于相关实体的批量加载。您可以在 GraphQL Java 文档 中找到完整详细信息。以下是其工作原理的摘要
-
在
DataLoaderRegistry
中注册DataLoader
,该注册表可以加载实体,前提是给定唯一的键。 -
DataFetcher
可以访问DataLoader
并使用它们通过 id 加载实体。 -
DataLoader
通过返回一个 future 来延迟加载,以便可以批量完成。 -
DataLoader
维护一个已加载实体的每个请求缓存,这可以进一步提高效率。
BatchLoaderRegistry
GraphQL Java 中完整的批量加载机制需要实现多个 BatchLoader
接口之一,然后将其包装并作为 DataLoader
与名称一起注册到 DataLoaderRegistry
中。
Spring GraphQL 中的 API 略有不同。对于注册,只有一个中央 BatchLoaderRegistry
公开工厂方法和一个构建器来创建和注册任意数量的批量加载函数
@Configuration
public class MyConfig {
public MyConfig(BatchLoaderRegistry registry) {
registry.forTypePair(Long.class, Author.class).registerMappedBatchLoader((authorIds, env) -> {
// return Mono<Map<Long, Author>
});
// more registrations ...
}
}
Boot Starter 声明一个 BatchLoaderRegistry
bean,您可以将其注入到您的配置中(如上所示)或任何组件(如控制器)中以注册批量加载函数。然后,BatchLoaderRegistry
将注入到 DefaultExecutionGraphQlService
中,它确保每个请求的 DataLoader
注册。
默认情况下,DataLoader
名称基于目标实体的类名。这允许 @SchemaMapping
方法声明一个具有泛型类型的 DataLoader 参数,而无需指定名称。但是,如果需要,可以通过 BatchLoaderRegistry
构建器自定义名称以及其他 DataLoaderOptions
。
要全局配置默认的 DataLoaderOptions
,以用作任何注册的起点,您可以覆盖 Boot 的 BatchLoaderRegistry
bean 并使用 DefaultBatchLoaderRegistry
的构造函数,该构造函数接受 Supplier<DataLoaderOptions>
。
在许多情况下,在加载相关实体时,您可以使用 @BatchMapping 控制器方法,它们是直接使用 BatchLoaderRegistry
和 DataLoader
的快捷方式并取代了它们的需要。
BatchLoaderRegistry
还提供了其他重要优势。它支持从批量加载函数和 @BatchMapping
方法访问相同的 GraphQLContext
,并确保 上下文传播 到它们。这就是为什么应用程序应该使用它的原因。可以执行您自己的 DataLoader
注册,但此类注册将放弃上述优势。
测试批量加载
首先让 BatchLoaderRegistry
在 DataLoaderRegistry
上执行注册
BatchLoaderRegistry batchLoaderRegistry = new DefaultBatchLoaderRegistry();
// perform registrations...
DataLoaderRegistry dataLoaderRegistry = DataLoaderRegistry.newRegistry().build();
batchLoaderRegistry.registerDataLoaders(dataLoaderRegistry, graphQLContext);
现在您可以按如下方式访问和测试各个 DataLoader
:
DataLoader<Long, Book> loader = dataLoaderRegistry.getDataLoader(Book.class.getName());
loader.load(1L);
loader.loadMany(Arrays.asList(2L, 3L));
List<Book> books = loader.dispatchAndJoin(); // actual loading
assertThat(books).hasSize(3);
assertThat(books.get(0).getName()).isEqualTo("...");
// ...