Request Execution
ExecutionGraphQlService
是调用 GraphQL Java 来执行请求的主要 Spring 抽象。诸如 HTTP
等基础传输会委派给 ExecutionGraphQlService
来处理请求。
ExecutionGraphQlService
is the main Spring abstraction to call GraphQL Java to execute
requests. Underlying transports, such as the HTTP, delegate to
ExecutionGraphQlService
to handle requests.
主实现 DefaultExecutionGraphQlService
使用 GraphQlSource
配置,以访问 graphql.GraphQL
实例进行调用。
The main implementation, DefaultExecutionGraphQlService
, is configured with a
GraphQlSource
for access to the graphql.GraphQL
instance to invoke.
GraphQLSource
GraphQlSource
是公开 graphql.GraphQL
实例以使用的契约,它还包含一个创建该实例的构建器 API。默认构建器可通过 GraphQlSource.schemaResourceBuilder()
获得。
GraphQlSource
is a contract to expose the graphql.GraphQL
instance to use that also
includes a builder API to build that instance. The default builder is available via
GraphQlSource.schemaResourceBuilder()
.
Boot Starter
会创建一个此构建器的实例,并进一步将其初始化为 load schema files
,从一个可配置的位置(https://docs.spring.io/spring-boot/docs/{spring-boot-version}/reference/html/application-properties.html#appendix.application-properties.web[公开属性])应用于 GraphQlSource.Builder
,以检测 `RuntimeWiringConfigurer
` bean、` Instrumentation` bean(用于 GraphQL 指标)和 DataFetcherExceptionResolver
及 SubscriptionExceptionResolver
bean(用于 exception resolution
)。要进行进一步的自定义,你还可以声明一个 GraphQlSourceBuilderCustomizer
bean,例如:
The Boot Starter creates an instance of this builder and further initializes it
to load schema files from a configurable location,
to expose properties
to apply to GraphQlSource.Builder
, to detect
RuntimeWiringConfigurer
beans,
Instrumentation beans for
GraphQL metrics,
and DataFetcherExceptionResolver
and SubscriptionExceptionResolver
beans for
exception resolution. For further customizations, you can also
declare a GraphQlSourceBuilderCustomizer
bean, for example:
@Configuration(proxyBeanMethods = false)
class GraphQlConfig {
@Bean
public GraphQlSourceBuilderCustomizer sourceBuilderCustomizer() {
return (builder) ->
builder.configureGraphQl(graphQlBuilder ->
graphQlBuilder.executionIdProvider(new CustomExecutionIdProvider()));
}
}
Schema Resources
GraphQlSource.Builder
可以使用一个或多个要解析和合并在一起的 Resource
实例进行配置。这意味着模式文件可以从几乎任何位置加载。
GraphQlSource.Builder
can be configured with one or more Resource
instances to be
parsed and merged together. That means schema files can be loaded from just about any
location.
默认情况下,Boot starterhttps://docs.spring.io/spring-boot/docs/{spring-boot-version}/reference/html/web.html#web.graphql.schema[查找后缀为“.graphqls”或“.gqls”的架构文件]在位置 classpath:graphql/**`下的位置(通常为 `src/main/resources/graphql
)。您还可以使用文件系统位置,或 Spring `Resource`层次结构支持的任何位置,包括从远程位置、从存储或从内存中加载架构文件的自定义实现。
By default, the Boot starter
looks for schema files with extensions
".graphqls" or ".gqls" under the location classpath:graphql/**
, which is typically
src/main/resources/graphql
. You can also use a file system location, or any location
supported by the Spring Resource
hierarchy, including a custom implementation that
loads schema files from remote locations, from storage, or from memory.
使用`classpath*:graphql/**/`查找多处类位置中的模式文件,例如跨多个模块。 |
Use |
Schema Creation
默认情况下,GraphQlSource.Builder`使用 GraphQL Java `SchemaGenerator`创建 `graphql.schema.GraphQLSchema
。这适用于典型用途,但如果您需要使用不同的生成器(例如,对于 federation),您可以注册 `schemaFactory`回调:
By default, GraphQlSource.Builder
uses the GraphQL Java SchemaGenerator
to create the
graphql.schema.GraphQLSchema
. This works for typical use, but if you need to use a
different generator, e.g. for federation, you can register a
schemaFactory
callback:
GraphQlSource.Builder builder = ...
builder.schemaResources(..)
.configureRuntimeWiring(..)
.schemaFactory((typeDefinitionRegistry, runtimeWiring) -> {
// create GraphQLSchema
})
GraphQlSource section 说明了如何使用 Spring Boot 来配置它。
The GraphQlSource section explains how to configure that with Spring Boot.
有关 Apollo Federation 的示例,请参见 federation-jvm-spring-example。
For an example with Apollo Federation, see federation-jvm-spring-example.
RuntimeWiringConfigurer
你可以使用 RuntimeWiringConfigurer
注册:
You can use RuntimeWiringConfigurer
to register:
-
Custom scalar types.
-
Directives handling code.
-
Default
TypeResolver
for interface and union types. -
DataFetcher
for a field although applications will typically use Annotated Controllers, and those are detected and registered asDataFetcher`s by `AnnotatedControllerConfigurer
, which is aRuntimeWiringConfigurer
. The Boot Starter automatically registersAnnotatedControllerConfigurer
.
GraphQL Java 服务器应用程序仅使用 Jackson 将数据映射序列化为和从数据映射反序列化。客户端输入被解析成一个映射。服务器输出基于字段选择集组装到一个映射中。这意味着您不能依靠 Jackson 序列化/反序列化注释。您可以使用 custom scalar types 代替。 |
GraphQL Java, server applications use Jackson only for serialization to and from maps of data. Client input is parsed into a map. Server output is assembled into a map based on the field selection set. This means you can’t rely on Jackson serialization/deserialization annotations. Instead, you can use custom scalar types. |
Boot Starter检测类型为 `RuntimeWiringConfigurer`的 bean,并将其注册在 `GraphQlSource.Builder`中。这意味着,在大多数情况下,您的配置中会出现类似以下内容:
The Boot Starter detects beans of type RuntimeWiringConfigurer
and
registers them in the GraphQlSource.Builder
. That means in most cases, you’ll' have
something like the following in your configuration:
@Configuration
public class GraphQlConfig {
@Bean
public RuntimeWiringConfigurer runtimeWiringConfigurer(BookRepository repository) {
GraphQLScalarType scalarType = ... ;
SchemaDirectiveWiring directiveWiring = ... ;
DataFetcher dataFetcher = QuerydslDataFetcher.builder(repository).single();
return wiringBuilder -> wiringBuilder
.scalar(scalarType)
.directiveWiring(directiveWiring)
.type("Query", builder -> builder.dataFetcher("book", dataFetcher));
}
}
如果你需要添加 WiringFactory
,例如为了进行考虑架构定义的注册,可以实现接受 RuntimeWiring.Builder
和输出 List<WiringFactory>
的备用 configure
方法。这允许你添加任意数量的工厂,然后按顺序调用它们。
If you need to add a WiringFactory
, e.g. to make registrations that take into account
schema definitions, implement the alternative configure
method that accepts both the
RuntimeWiring.Builder
and an output List<WiringFactory>
. This allows you to add any
number of factories that are then invoked in sequence.
TypeResolver
GraphQlSource.Builder
将 ClassNameTypeResolver
注册为要用于通过 xref:request-execution.adoc#execution.graphqlsource.runtimewiring-configurer[RuntimeWiringConfigurer
尚未进行此类注册的 GraphQL 接口和联盟的默认 TypeResolver
。在 GraphQL Java 中,TypeResolver
的目的是为从 DataFetcher
返回的值确定适用于 GraphQL 接口或联盟字段的 GraphQL 对象类型。
GraphQlSource.Builder
registers ClassNameTypeResolver
as the default TypeResolver
to use for GraphQL Interfaces and Unions that don’t already have such a registration
through a RuntimeWiringConfigurer
. The purpose of
a TypeResolver
in GraphQL Java is to determine the GraphQL Object type for values
returned from the DataFetcher
for a GraphQL Interface or Union field.
ClassNameTypeResolver
尝试将值的简单类名与 GraphQL 对象类型进行匹配,如果匹配失败,它还会遍历其超类型,包括基类和接口,寻找匹配项。ClassNameTypeResolver
提供了一个选项,用于配置名称提取函数,以及类到 GraphQL 对象类型名称映射,这应该有助于涵盖更多特殊情况:
ClassNameTypeResolver
tries to match the simple class name of the value to a GraphQL
Object Type and if it is not successful, it also navigates its super types including
base classes and interfaces, looking for a match. ClassNameTypeResolver
provides an
option to configure a name extracting function along with Class
to GraphQL Object type
name mappings that should help to cover more corner cases:
GraphQlSource.Builder builder = ...
ClassNameTypeResolver classNameTypeResolver = new ClassNameTypeResolver();
classNameTypeResolver.setClassNameExtractor((klass) -> {
// Implement Custom ClassName Extractor here
});
builder.defaultTypeResolver(classNameTypeResolver);
GraphQlSource section 说明了如何使用 Spring Boot 来配置它。
The GraphQlSource section explains how to configure that with Spring Boot.
Directives
GraphQL 语言支持指令,该指令“描述了 GraphQL 文档中的备用运行时执行和类型验证行为”。指令类似于 Java 中的注释,但在 GraphQL 文档中的类型、字段、片段和操作上声明。
The GraphQL language supports directives that "describe alternate runtime execution and type validation behavior in a GraphQL document". Directives are similar to annotations in Java but declared on types, fields, fragments and operations in a GraphQL document.
GraphQL Java 提供 `SchemaDirectiveWiring`契约,帮助应用程序检测和处理指令。有关更多详细信息,请参阅 GraphQL Java 文档中的https://www.graphql-java.com/documentation/sdl-directives/[架构指令]。
GraphQL Java provides the SchemaDirectiveWiring
contract to help applications detect
and handle directives. For more details, see
Schema Directives in the
GraphQL Java documentation.
在 Spring GraphQL 中,您可以通过RuntimeWiringConfigurer
来注册 SchemaDirectiveWiring
。 Boot Starter 检测到此类 Bean,因此您可能拥有类似以下的内容:
In Spring GraphQL you can register a SchemaDirectiveWiring
through a
RuntimeWiringConfigurer
. The Boot Starter detects
such beans, so you might have something like:
@Configuration
public class GraphQlConfig {
@Bean
public RuntimeWiringConfigurer runtimeWiringConfigurer() {
return builder -> builder.directiveWiring(new MySchemaDirectiveWiring());
}
}
有关指令支持的示例,请查看 Extended Validation for Graphql Java 库。 |
For an example of directives support check out the Extended Validation for Graphql Java library. |
Schema Transformation
如果您希望在创建模式之后遍历并转换模式,并对模式进行更改,您可以通过`builder.schemaResources(..).typeVisitorsToTransformSchema(..)来注册 `graphql.schema.GraphQLTypeVisitor
。请记住,这样做比Schema Traversal的开销更大,因此除非您需要对模式进行更改,否则通常更喜欢遍历而非转换。
You can register a graphql.schema.GraphQLTypeVisitor
via
builder.schemaResources(..).typeVisitorsToTransformSchema(..)
if you want to traverse
and transform the schema after it is created, and make changes to the schema. Keep in mind
that this is more expensive than Schema Traversal so generally
prefer traversal to transformation unless you need to make schema changes.
Schema Traversal
如果您希望在创建模式之后遍历模式,并可能对`GraphQLCodeRegistry`应用更改,您可以通过`builder.schemaResources(..).typeVisitors(..)来注册 `graphql.schema.GraphQLTypeVisitor
。但是,请记住,此类访问器不能更改模式。如果您需要对模式进行更改,请参见Schema Transformation。
You can register a graphql.schema.GraphQLTypeVisitor
via
builder.schemaResources(..).typeVisitors(..)
if you want to traverse the schema after
it is created, and possibly apply changes to the GraphQLCodeRegistry
. Keep in mind,
however, that such a visitor cannot change the schema. See
Schema Transformation, if you need to make changes to the schema.
Schema Mapping Inspection
如果查询、突变或订阅操作没有 DataFetcher
,它将不会返回任何数据,也不会执行任何有用的操作。同样,如果通过 DataFetcher
注册显式涵盖某个操作返回的架构类型上的字段,或者通过默认 PropertyDataFetcher
隐式涵盖该字段(PropertyDataFetcher
寻找匹配的 Java 对象属性),该字段总是 null
。
If a query, mutation, or subscription operation does not have a DataFetcher
, it won’t
return any data, and won’t do anything useful. Likewise, fields on schema types returned
by an operation that are covered neither explicitly through a DataFetcher
registration, nor implicitly by the default PropertyDataFetcher
, which looks for a
matching Java object property, will always be null
.
GraphQL Java 不执行检查以确保每个架构字段都被涵盖,这可能导致出现根据测试覆盖范围无法发现的差距。在运行时,你可能会收到“安静的”null
,或者在字段不可为空时收到错误。作为较低级别的库,GraphQL Java 根本不了解足够多的 DataFetcher
实现及其返回类型,因此无法将架构类型结构与 Java 对象结构进行比较。
GraphQL Java does not perform checks to ensure every schema field is covered, and that
can result in gaps that might not be discovered depending on test coverage. At runtime
you may get a "silent" null
, or an error if the field is not nullable. As a lower level
library, GraphQL Java simply does not know enough about DataFetcher
implementations and
their return types, and therefore can’t compare schema type structure against Java object
structure.
Spring for GraphQL 定义了 SelfDescribingDataFetcher
接口,以允许`DataFetcher`公开返回类型信息。所有 Spring DataFetcher`实现都实现了此接口。它包括用于Annotated Controllers的实现,以及用于Querydsl和 Query by ExampleSpring Data 存储库的实现。对于带注释的控制器,返回类型是从
@SchemaMapping`方法的已声明返回类型中派生的。
Spring for GraphQL defines the SelfDescribingDataFetcher
interface to allow a
DataFetcher
to expose return type information. All Spring DataFetcher
implementations
implement this interface. That includes those for Annotated Controllers, and those for
Querydsl and Query by Example Spring Data repositories. For annotated
controllers, the return type is derived from the declared return type on a
@SchemaMapping
method.
在启动时,Spring for GraphQL 可以检查架构字段、DataFetcher
注册以及从 DataFetcher
实现返回的 Java 对象的属性,以检查所有架构字段是否通过明确注册的 DataFetcher
或匹配的 Java 对象属性得到涵盖。检查还会执行反向检查,以查找不存在的架构字段对应的 DataFetcher
注册。
On startup, Spring for GraphQL can inspect schema fields, DataFetcher
registrations,
and the properties of Java objects returned from DataFetcher
implementations to check
if all schema fields are covered either by an explicitly registered DataFetcher
, or
a matching Java object property. The inspection also performs a reverse check looking for
DataFetcher
registrations against schema fields that don’t exist.
若要启用架构映射检查:
To enable inspection of schema mappings:
GraphQlSource.Builder builder = ...
builder.schemaResources(..)
.inspectSchemaMappings(report -> {
logger.debug(report);
})
以下是示例报告:
Below is an example report:
GraphQL schema inspection: Unmapped fields: {Book=[title], Author[firstName, lastName]} (1) Unmapped registrations: {Book.reviews=BookController#reviews[1 args]} 2 Skipped types: [BookOrAuthor] (3)
1 | List of schema fields and their source types that are not mapped |
2 | List of DataFetcher registrations on fields that don’t exist |
3 | List of schema types that are skipped, as explained next |
模式字段检查可以执行的操作存在限制,特别是在 Java 类型信息不足的情况下。如果一个带注释的控制器方法被声明返回 java.lang.Object
,或者如果返回类型具有一个未指定泛型参数(例如 List<?>
),或者如果 DataFetcher
没有实现 SelfDescribingDataFetcher
,甚至不知道返回类型,就会出现这种情况。在这种情况下,Java 对象类型结构仍然未知,并且模式类型在生成的报告中被列为已跳过。对于每个被跳过的类型,都会记录一条 DEBUG 消息以指示为什么被跳过。
There are limits to what schema field inspection can do, in particular when there is
insufficient Java type information. This is the case if an annotated controller method is
declared to return java.lang.Object
, or if the return type has an unspecified generic
parameter such as List<?>
, or if the DataFetcher
does not implement
SelfDescribingDataFetcher
and the return type is not even known. In such cases, the
Java object type structure remains unknown, and the schema type is listed as skipped in
the resulting report. For every skipped type, a DEBUG message is logged to indicate why
it was skipped.
模式联合类型始终被跳过,因为控制器方法没有办法在 Java 中声明这样的返回类型,并且 Java 类型结构是未知的。
Schema union types are always skipped because there is no way for a controller method to declare such a return type in Java, and the Java type structure is unknown.
模式接口类型仅受支持到直接声明的字段,这些字段与 SelfDescribingDataFetcher
声明的 Java 类型上的属性进行比较。具体实现上的其他字段不会被检查。这可以在未来版本中进行改进,以检查模式 interface
实现类型并尝试在已声明的 Java 返回类型的子类型中找到匹配项。
Schema interface types are supported only as far as fields declared directly, which are
compared against properties on the Java type declared by a SelfDescribingDataFetcher
.
Additional fields on concrete implementations are not inspected. This could be improved
in a future release to also inspect schema interface
implementation types and to try
to find a match among subtypes of the declared Java return type.
Operation Caching
GraphQL Java 必须在执行操作之前 _parse_并 _validate_该操作。这可能会极大地影响性能。为了避免重新解析和验证的需要,应用程序可以配置一个缓存和重复使用 Document 实例的 PreparsedDocumentProvider
。https://www.graphql-java.com/documentation/execution/#query-caching[GraphQL Java 文档]通过 `PreparsedDocumentProvider`提供了有关查询缓存的更多详细信息。
GraphQL Java must parse and validate an operation before executing it. This may impact
performance significantly. To avoid the need to re-parse and validate, an application may
configure a PreparsedDocumentProvider
that caches and reuses Document instances. The
GraphQL Java docs provide more details on
query caching through a PreparsedDocumentProvider
.
在 Spring GraphQL 中,你可以通过 GraphQlSource.Builder#configureGraphQl
注册一个 PreparsedDocumentProvider
:
In Spring GraphQL you can register a PreparsedDocumentProvider
through
GraphQlSource.Builder#configureGraphQl
:
.
// Typically, accessed through Spring Boot's GraphQlSourceBuilderCustomizer
GraphQlSource.Builder builder = ...
// Create provider
PreparsedDocumentProvider provider = ...
builder.schemaResources(..)
.configureRuntimeWiring(..)
.configureGraphQl(graphQLBuilder -> graphQLBuilder.preparsedDocumentProvider(provider))
GraphQlSource section 说明了如何使用 Spring Boot 来配置它。
The GraphQlSource section explains how to configure that with Spring Boot.
Reactive DataFetcher
默认的 GraphQlSource
构建器支持 DataFetcher
返回 Mono
或 Flux
,这些 DataFetcher
将其调整为 CompletableFuture
,其中 Flux
值被聚合并转换为 List,除非请求是 GraphQL 订阅请求,在这种情况下,返回值仍然是一个 Publisher
,用于流式传输 GraphQL 响应。
The default GraphQlSource
builder enables support for a DataFetcher
to return Mono
or Flux
which adapts those to a CompletableFuture
where Flux
values are aggregated
and turned into a List, unless the request is a GraphQL subscription request,
in which case the return value remains a Reactive Streams Publisher
for streaming
GraphQL responses.
响应式 `DataFetcher`可以依赖从传输层传播的 Reactor 上下文,例如来自 WebFlux 请求处理,请参见WebFlux Context。
A reactive DataFetcher
can rely on access to Reactor context propagated from the
transport layer, such as from a WebFlux request handling, see
WebFlux Context.
Context Propagation
Spring for GraphQL 提供了支持,以透明地从HTTP传播上下文,通过 GraphQL Java 传播到 DataFetcher`和其他它调用的组件。这包括来自 Spring MVC 请求处理线程的 `ThreadLocal`上下文和来自 WebFlux 处理管道的 Reactor `Context
。
Spring for GraphQL provides support to transparently propagate context from the
HTTP, through GraphQL Java, and to DataFetcher
and other components it
invokes. This includes both ThreadLocal
context from the Spring MVC request handling
thread and Reactor Context
from the WebFlux processing pipeline.
WebMvc
一个 DataFetcher
和其他由 GraphQL Java 调用的组件不一定总是在与 Spring MVC 处理程序相同的线程上执行,例如,如果一个异步 xref:transports.adoc#server.interception[WebGraphQlInterceptor
或 DataFetcher
切换到了一个不同的线程。
A DataFetcher
and other components invoked by GraphQL Java may not always execute on
the same thread as the Spring MVC handler, for example if an asynchronous
WebGraphQlInterceptor
or DataFetcher
switches to a
different thread.
Spring for GraphQL 支持从 Servlet 容器线程向 DataFetcher
和 GraphQL Java 调用的其他组件执行的线程传播 ThreadLocal
值。要做到这一点,应用程序需要为感兴趣的 ThreadLocal
值实现 io.micrometer.context.ThreadLocalAccessor
:
Spring for GraphQL supports propagating ThreadLocal
values from the Servlet container
thread to the thread a DataFetcher
and other components invoked by GraphQL Java to
execute on. To do this, an application needs to implement
io.micrometer.context.ThreadLocalAccessor
for a ThreadLocal
values of interest:
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
实例手动注册 ThreadLocalAccessor
,该实例可以通过 io.micrometer.context.ContextRegistry#getInstance()
访问。你也可以通过 java.util.ServiceLoader
机制自动注册它。
You can register a ThreadLocalAccessor
manually on startup with the global
ContextRegistry
instance, which is accessible via
io.micrometer.context.ContextRegistry#getInstance()
. You can also register it
automatically through the java.util.ServiceLoader
mechanism.
WebFlux
Reactive DataFetcher
可以依赖对 Reactor 上下文的访问,该上下文源自 WebFlux 请求处理链。这包括由 WebGraphQlInterceptor组件添加的 Reactor 上下文。
A Reactive DataFetcher
can rely on access to Reactor context that
originates from the WebFlux request handling chain. This includes Reactor context
added by WebGraphQlInterceptor components.
Exceptions
在 GraphQL Java 中,DataFetcherExceptionHandler
决定如何将数据提取中的异常表示为响应的“errors”部分。应用程序只能注册一个处理程序。
In GraphQL Java, DataFetcherExceptionHandler
decides how to represent exceptions from
data fetching in the "errors" section of the response. An application can register a
single handler only.
Spring for GraphQL 注册了一个 DataFetcherExceptionHandler
,该 DataFetcherExceptionHandler
提供了默认处理并启用了 DataFetcherExceptionResolver
契约。一个应用程序可以通过 xref:request-execution.adoc#execution.graphqlsource[GraphQLSource
构建工具注册任意数量的解析器,这些解析器按顺序注册,直到其中一个将 Exception
解析为一个 List<graphql.GraphQLError>
。Spring Boot 启动器检测了此类型的 bean。
Spring for GraphQL registers a DataFetcherExceptionHandler
that provides default
handling and enables the DataFetcherExceptionResolver
contract. An application can
register any number of resolvers via GraphQLSource
builder and those are in
order until one them resolves the Exception
to a List<graphql.GraphQLError>
.
The Spring Boot starter detects beans of this type.
DataFetcherExceptionResolverAdapter
是一个包含受保护方法 resolveToSingleError
和 resolveToMultipleErrors
的便捷基类。
DataFetcherExceptionResolverAdapter
is a convenient base class with protected methods
resolveToSingleError
and resolveToMultipleErrors
.
Annotated Controllers编程模型能够使用带灵活方法签名的带注释的异常处理程序方法来处理数据提取异常,请参见@GraphQlExceptionHandler
了解详细信息。
The Annotated Controllers programming model enables handling data fetching exceptions with
annotated exception handler methods with a flexible method signature, see
@GraphQlExceptionHandler
for details.
GraphQLError
可以根据 GraphQL Java graphql.ErrorClassification
或定义以下内容的 Spring GraphQL ErrorType
被分配到一个类别:
A GraphQLError
can be assigned to a category based on the GraphQL Java
graphql.ErrorClassification
, or the Spring GraphQL ErrorType
, which defines the following:
-
BAD_REQUEST
-
UNAUTHORIZED
-
FORBIDDEN
-
NOT_FOUND
-
INTERNAL_ERROR
如果异常仍然未解决,则默认情况下它会被归类为 INTERNAL_ERROR
,并带有包含类别名称和来自 DataFetchingEnvironment
的 executionId
的通用消息。该消息故意不透明,以避免泄漏实现细节。应用程序可以使用 DataFetcherExceptionResolver
来自定义错误详细信息。
If an exception remains unresolved, by default it is categorized as an INTERNAL_ERROR
with a generic message that includes the category name and the executionId
from
DataFetchingEnvironment
. The message is intentionally opaque to avoid leaking
implementation details. Applications can use a DataFetcherExceptionResolver
to customize
error details.
未解决的异常会与 executionId
一起记录在 ERROR 级别,以与发送到客户端的错误相关联。已解决的异常记录在 DEBUG 级别。
Unresolved exception are logged at ERROR level along with the executionId
to correlate
to the error sent to the client. Resolved exceptions are logged at DEBUG level.
Request Exceptions
在解析请求时,GraphQL Java 引擎可能会遇到验证或其他错误,这反过来会阻止请求执行。在这种情况下,响应包含一个带 null
的“data”键和一个或多个全局请求级别“errors”,即没有字段路径。
The GraphQL Java engine may run into validation or other errors when parsing the request
and that in turn prevent request execution. In such cases, the response contains a
"data" key with null
and one or more request-level "errors" that are global, i.e. not
having a field path.
DataFetcherExceptionResolver
无法处理此类全局错误,因为它们在执行开始之前和任何 DataFetcher
被调用之前就被引发。一个应用程序可以使用传输层拦截器来检查和转换 ExecutionResult
中的错误。参见 xref:transports.adoc#server.interception.web[WebGraphQlInterceptor
中的示例。
DataFetcherExceptionResolver
cannot handle such global errors because they are raised
before execution begins and before any DataFetcher
is invoked. An application can use
transport level interceptors to inspect and transform errors in the ExecutionResult
.
See examples under WebGraphQlInterceptor
.
Subscription Exceptions
订阅请求的 Publisher
可能带有错误信号完成,在这种情况下,底层传输(例如 WebSocket)会发送带有 GraphQL 错误列表的最终“error”类型消息。
The Publisher
for a subscription request may complete with an error signal in which case
the underlying transport (e.g. WebSocket) sends a final "error" type message with a list
of GraphQL errors.
DataFetcherExceptionResolver
无法解析订阅 Publisher
的错误,因为数据 DataFetcher
仅最初创建 Publisher
。之后,传输会订阅可能带有错误完成的 Publisher
。
DataFetcherExceptionResolver
cannot resolve errors from a subscription Publisher
,
since the data DataFetcher
only creates the Publisher
initially. After that, the
transport subscribes to the Publisher
that may then complete with an error.
应用程序可以注册 SubscriptionExceptionResolver
以便解析订阅 Publisher
的异常,从而将其解析为要发送给客户端的 GraphQL 错误。
An application can register a SubscriptionExceptionResolver
in order to resolve
exceptions from a subscription Publisher
in order to resolve those to GraphQL errors
to send to the client.
Pagination
GraphQL Cursor Connection specification定义了一种通过一次返回一个子集的项来导航大型结果集的方法,每个项都与光标配对,客户端可以使用该光标来请求在引用项之前或之后的更多项。
The GraphQL Cursor Connection specification defines a way to navigate large result sets by returning a subset of items at a time where each item is paired with a cursor that clients can use to request more items before or after the referenced item.
该规范将此模式称为“连接”。名称以 Connection 结尾的模式类型是一个 Connection 类型,代表分页结果集。所有 ~Connection
类型都包含一个“edges”字段,其中 ~Edge
类型将实际项与光标配对,以及一个“pageInfo”字段(带布尔标记来指示是否有更多向前和向后的项)。
The specification calls the pattern "Connections". A schema type with a name that ends
on Connection is a Connection Type that represents a paginated result set. All ~Connection
types contain an "edges" field where ~Edge
type pairs the actual item with a cursor, as
well as a "pageInfo" field with boolean flags to indicate if there are more items forward
and backward.
Connection Types
必须为需要分页的每种类型创建 Connection
类型定义,从而向模式添加样板和噪声。Spring for GraphQL 提供 ConnectionTypeDefinitionConfigurer
来在启动时添加这些类型,如果已解析的模式文件中没有这些类型。这意味着在模式中只需要以下内容:
Connection
type definitions must be created for every type that needs pagination, adding
boilerplate and noise to the schema. Spring for GraphQL provides
ConnectionTypeDefinitionConfigurer
to add these types on startup, if not already
present in the parsed schema files. That means in the schema you only need this:
Query {
books(first:Int, after:String, last:Int, before:String): BookConnection
}
type Book {
id: ID!
title: String!
}
请注意由规范定义的前向分页参数 first
和 after
,客户端可以使用这些参数请求给定光标之后的第一个 N 项,而 last
和 before
是后向分页参数,用于请求给定光标之前的最后一个 N 项。
Note the spec-defined forward pagination arguments first
and after
that clients can use
to request the first N items after the given cursor, while last
and before
are backward
pagination arguments to request the last N items before the given cursor.
接下来,按如下方式配置 ConnectionTypeDefinitionConfigurer
:
Next, configure ConnectionTypeDefinitionConfigurer
as follows:
GraphQlSource.schemaResourceBuilder()
.schemaResources(..)
.typeDefinitionConfigurer(new ConnectionTypeDefinitionConfigurer)
然后,以下类型定义将被透明地添加到模式中:
and the following type definitions will be transparently added to the schema:
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
。
The Boot Starter registers ConnectionTypeDefinitionConfigurer
by default.
ConnectionAdapter
在模式中提供Connection Types之后,你还需要等效的 Java 类型。GraphQL Java 提供了这些类型,包括通用 Connection`和`Edge
,以及 PageInfo
。
Once Connection Types are available in the schema, you also need
equivalent Java types. GraphQL Java provides those, including generic Connection
and
Edge
, as well as a PageInfo
.
一种选择是用 Connection
填充它并从控制器方法或 DataFetcher
返回它。但是,这需要样板代码来创建 Connection
,创建光标,将每个项包装为 Edge
,并创建 PageInfo
。此外,你可能已经具有底层分页机制,例如在使用 Spring Data 存储库时。
One option is to populate a Connection
and return it from your controller method or
DataFetcher
. However, this requires boilerplate code to create the Connection
,
creating cursors, wrapping each item as an Edge
, and creating the PageInfo
.
Moreover, you may already have an underlying pagination mechanism such as when using
Spring Data repositories.
Spring for GraphQL 定义 ConnectionAdapter
契约以将项容器调整为 Connection
。通过 DataFetcher
修饰符应用适配器,该修饰符又通过 ConnectionFieldTypeVisitor
安装。你可以按如下方式配置它:
Spring for GraphQL defines the ConnectionAdapter
contract to adapt a container of items
to Connection
. Adapters are applied through a DataFetcher
decorator that is in turn
installed through a ConnectionFieldTypeVisitor
. You can configure it as follows:
ConnectionAdapter adapter = ... ;
GraphQLTypeVisitor visitor = ConnectionFieldTypeVisitor.create(List.of(adapter)) (1)
GraphQlSource.schemaResourceBuilder()
.schemaResources(..)
.typeDefinitionConfigurer(..)
.typeVisitors(List.of(visitor)) (2)
1 | Create type visitor with one or more `ConnectionAdapter`s. |
2 | Resister the type visitor. |
有一些built-inConnectionAdapter`s for Spring Data’s
`Window`和 `Slice
。您还可以创建自己的自定义适配器。ConnectionAdapter`实现依赖于 `CursorStrategy
来为返回项创建光标。相同的策略也用于支持包含分页输入的Subrange
控制器方法参数。
There are built-in ConnectionAdapter`s for Spring Data’s
`Window
and Slice
. You can also create your own custom adapter. ConnectionAdapter
implementations rely on a CursorStrategy
to
create cursors for returned items. The same strategy is also used to support the
Subrange
controller method argument that contains
pagination input.
CursorStrategy
CursorStrategy
是一个契约,用于对引用大型结果集内项位置的 String 光标进行编码和解码。光标可以基于索引或 keyset。
CursorStrategy
is a contract to encode and decode a String cursor that refers to the
position of an item within a large result set. The cursor can be based on an index or
on a keyset.
ConnectionAdapter
使用此方法来对返回项的光标进行编码。Annotated Controllers方法、Querydsl存储库和 Query by Example存储库使用它来从分页请求中解码光标,并创建一个`Subrange`。
A ConnectionAdapter
uses this to encode cursors for returned items.
Annotated Controllers methods, Querydsl repositories, and Query by Example
repositories use it to decode cursors from pagination requests, and create a Subrange
.
CursorEncoder
是一个相关的契约,它进一步对 String 光标进行编码和解码,使其对客户端不透明。EncodingCursorStrategy
将 CursorStrategy
与 CursorEncoder
结合在一起。你可以使用 Base64CursorEncoder
、NoOpEncoder
或创建自己的编码器。
CursorEncoder
is a related contract that further encodes and decodes String cursors to
make them opaque to clients. EncodingCursorStrategy
combines CursorStrategy
with a
CursorEncoder
. You can use Base64CursorEncoder
, NoOpEncoder
or create your own.
对于 Spring Data`ScrollPosition`有一个 built-inCursorStrategy
。Boot Starter在存在 Spring Data 时使用`Base64Encoder`注册 CursorStrategy<ScrollPosition>
。
There is a built-in CursorStrategy
for the Spring Data
ScrollPosition
. The Boot Starter registers a CursorStrategy<ScrollPosition>
with
Base64Encoder
when Spring Data is present.
Sort
在 GraphQL 请求中提供排序信息没有标准方法。但是,分页依赖于稳定的排序顺序。你可以使用默认排序,或者从 GraphQL 参数中提取排序详细信息并公开输入类型。
There is no standard way to provide sort information in a GraphQL request. However, pagination depends on a stable sort order. You can use a default order, or otherwise expose input types and extract sort details from GraphQL arguments.
有built-in支持 Spring Data 的`Sort`作为控制器方法参数。为使其正常工作,你需要有一个 `SortStrategy`Bean。
There is built-in support for Spring Data’s Sort
as a controller
method argument. For this to work, you need to have a SortStrategy
bean.
Batch Loading
给定一个 Book
和它的 Author
,我们可以为一本书创建一个 DataFetcher
,并为它的作者创建另一个。这允许选择有或没有作者的书,但它意味着书和作者不会一起加载,当查询多本书时尤其效率低下,因为每本书的作者都是单独加载的。这就是所谓的 N+1 选择问题。
Given a Book
and its Author
, we can create one DataFetcher
for a book and another
for its author. This allows selecting books with or without authors, but it means books
and authors aren’t loaded together, which is especially inefficient when querying multiple
books as the author for each book is loaded individually. This is known as the N+1 select
problem.
DataLoader
GraphQL Java 提供了一个用于批量加载相关实体的 DataLoader
机制。您可以在 GraphQL Java 文档 中找到全部详细信息。以下是其工作原理的摘要:
GraphQL Java provides a DataLoader
mechanism for batch loading of related entities.
You can find the full details in the
GraphQL Java docs. Below is a
summary of how it works:
-
Register
DataLoader’s in the `DataLoaderRegistry
that can load entities, given unique keys. -
`DataFetcher’s can access `DataLoader’s and use them to load entities by id.
-
A
DataLoader
defers loading by returning a future so it can be done in a batch. -
`DataLoader’s maintain a per request cache of loaded entities that can further improve efficiency.
BatchLoaderRegistry
GraphQL Java 中完整的批量加载机制需要实现多个 BatchLoader
接口,然后将这些接口包装并注册为带有 DataLoaderRegistry
中名称的 DataLoader
。
The complete batching loading mechanism in GraphQL Java requires implementing one of
several BatchLoader
interface, then wrapping and registering those as DataLoader`s
with a name in the `DataLoaderRegistry
.
Spring GraphQL 中的 API 略有不同。对于注册,只有一个中心 BatchLoaderRegistry
,公开工厂方法和一个构建器来创建和注册任意数量的批量加载函数:
The API in Spring GraphQL is slightly different. For registration, there is only one,
central BatchLoaderRegistry
exposing factory methods and a builder to create and
register any number of batch loading functions:
@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
注册。
The Boot Starter declares a BatchLoaderRegistry
bean that you can inject into
your configuration, as shown above, or into any component such as a controller in order
register batch loading functions. In turn the BatchLoaderRegistry
is injected into
DefaultExecutionGraphQlService
where it ensures DataLoader
registrations per request.
默认情况下,DataLoader`名称基于目标实体的类命名。这允许
@SchemaMapping`方法使用通用类型声明DataLoader argument,而无需指定名称。但是,如有必要,可以通过`BatchLoaderRegistry`构建器自定义名称,以及其他`DataLoaderOptions`。
By default, the DataLoader
name is based on the class name of the target entity.
This allows an @SchemaMapping
method to declare a
DataLoader argument with a generic type, and
without the need for specifying a name. The name, however, can be customized through the
BatchLoaderRegistry
builder, if necessary, along with other DataLoaderOptions
.
要全局配置默认 DataLoaderOptions
以用作任何注册的起点,可以覆盖 Boot 的 BatchLoaderRegistry
bean,并使用接收 Supplier<DataLoaderOptions>
的 DefaultBatchLoaderRegistry
的构造器。
To configure default DataLoaderOptions
globally, to use as a starting point for any
registration, you can override Boot’s BatchLoaderRegistry
bean and use the constructor
for DefaultBatchLoaderRegistry
that accepts Supplier<DataLoaderOptions>
.
在许多情况下,在加载相关实体时,您可以使用@BatchMapping控制器方法,它们是一个快捷方式,可以替代直接使用 BatchLoaderRegistry`和 `DataLoader
。
For many cases, when loading related entities, you can use
@BatchMapping controller methods, which are a shortcut
for and replace the need to use BatchLoaderRegistry
and DataLoader
directly.
BatchLoaderRegistry`还提供了其他重要的好处。它支持从批处理加载函数和
@BatchMapping`方法访问相同的`GraphQLContext`,以及确保Context Propagation向它们提供访问。这就是为什么应用程序预期会使用它。您可能会直接执行您自己的`DataLoader`注册,但此类注册将放弃上述好处。
BatchLoaderRegistry
provides other important benefits too. It supports access to
the same GraphQLContext
from batch loading functions and from @BatchMapping
methods,
as well as ensures Context Propagation to them. This is why applications are expected
to use it. It is possible to perform your own DataLoader
registrations directly but
such registrations would forgo the above benefits.
Testing Batch Loading
首先让 BatchLoaderRegistry
对 DataLoaderRegistry
执行注册:
Start by having BatchLoaderRegistry
perform registrations on a DataLoaderRegistry
:
BatchLoaderRegistry batchLoaderRegistry = new DefaultBatchLoaderRegistry();
// perform registrations...
DataLoaderRegistry dataLoaderRegistry = DataLoaderRegistry.newRegistry().build();
batchLoaderRegistry.registerDataLoaders(dataLoaderRegistry, graphQLContext);
现在,你可以如下访问并测试单个 DataLoader
:
Now you can access and test individual `DataLoader’s as follows:
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("...");
// ...