請求執行
ExecutionGraphQlService
是呼叫 GraphQL Java 執行請求的主要 Spring 抽象概念。底層傳輸(例如 HTTP)委派給 ExecutionGraphQlService
來處理請求。
主要實作 DefaultExecutionGraphQlService
,配置了 GraphQlSource
以存取要調用的 graphql.GraphQL
實例。
GraphQLSource
GraphQlSource
是一個合約,用於公開要使用的 graphql.GraphQL
實例,其中還包括一個建構器 API 來建構該實例。預設建構器可透過 GraphQlSource.schemaResourceBuilder()
取得。
Boot Starter 建立此建構器的實例,並進一步初始化它以從可配置的位置載入 schema 檔案,公開屬性以應用於 GraphQlSource.Builder
,偵測 RuntimeWiringConfigurer
beans,用於 GraphQL 指標的 Instrumentation beans,以及用於例外解析的 DataFetcherExceptionResolver
和 SubscriptionExceptionResolver
beans。對於進一步的自訂,您也可以宣告 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()));
}
}
Schema 資源
GraphQlSource.Builder
可以配置一個或多個要解析和合併在一起的 Resource
實例。這表示 schema 檔案可以從幾乎任何位置載入。
預設情況下,Boot starter 尋找 schema 檔案,其副檔名為 ".graphqls" 或 ".gqls",位於 classpath:graphql/**
位置下,通常是 src/main/resources/graphql
。您也可以使用檔案系統位置,或 Spring Resource
階層支援的任何位置,包括從遠端位置、儲存空間或記憶體載入 schema 檔案的自訂實作。
使用 classpath*:graphql/**/ 在多個類別路徑位置(例如跨多個模組)中尋找 schema 檔案。 |
Schema 建立
預設情況下,GraphQlSource.Builder
使用 GraphQL Java SchemaGenerator
來建立 graphql.schema.GraphQLSchema
。這適用於典型用途,但如果您需要使用不同的產生器,您可以註冊 schemaFactory
回呼
GraphQlSource.Builder builder = ...
builder.schemaResources(..)
.configureRuntimeWiring(..)
.schemaFactory((typeDefinitionRegistry, runtimeWiring) -> {
// create GraphQLSchema
})
請參閱 GraphQlSource 章節,瞭解如何使用 Spring Boot 配置此項。
如果對聯邦感興趣,請參閱 聯邦 章節。
RuntimeWiringConfigurer
RuntimeWiringConfigurer
用於註冊以下項目非常有用
-
自訂純量類型。
-
處理 Directives 的程式碼。
-
直接
DataFetcher
註冊。 -
以及更多…
Spring 應用程式通常不需要執行直接 DataFetcher 註冊。相反,控制器方法透過 AnnotatedControllerConfigurer 註冊為 DataFetcher ,而 AnnotatedControllerConfigurer 是一個 RuntimeWiringConfigurer 。 |
GraphQL Java,伺服器應用程式僅使用 Jackson 進行序列化和從資料地圖序列化。用戶端輸入被解析為地圖。伺服器輸出根據欄位選擇集組裝成地圖。這表示您不能依賴 Jackson 序列化/反序列化註解。相反,您可以使用自訂純量類型。 |
Boot Starter 偵測 RuntimeWiringConfigurer
類型的 beans,並在 GraphQlSource.Builder
中註冊它們。這表示在大多數情況下,您的配置中會包含如下內容
@Configuration
public class GraphQlConfig {
@Bean
public RuntimeWiringConfigurer runtimeWiringConfigurer(BookRepository repository) {
GraphQLScalarType scalarType = ... ;
SchemaDirectiveWiring directiveWiring = ... ;
return wiringBuilder -> wiringBuilder
.scalar(scalarType)
.directiveWiring(directiveWiring);
}
}
如果您需要新增 WiringFactory
,例如進行考慮 schema 定義的註冊,請實作接受 RuntimeWiring.Builder
和輸出 List<WiringFactory>
的替代 configure
方法。這允許您新增任意數量的工廠,然後按順序調用這些工廠。
TypeResolver
GraphQlSource.Builder
註冊 ClassNameTypeResolver
作為預設 TypeResolver
,用於 GraphQL 介面和聯合,這些介面和聯合尚未透過 RuntimeWiringConfigurer
進行此類註冊。GraphQL Java 中 TypeResolver
的目的是確定從 GraphQL 介面或聯合欄位的 DataFetcher
返回的值的 GraphQL 物件類型。
ClassNameTypeResolver
嘗試將值的簡單類別名稱與 GraphQL 物件類型匹配,如果失敗,它也會導航其超類型,包括基底類別和介面,尋找匹配項。ClassNameTypeResolver
提供了一個選項,用於配置名稱擷取函數以及應有助於涵蓋更多邊角案例的 Class
到 GraphQL 物件類型名稱的映射
GraphQlSource.Builder builder = ...
ClassNameTypeResolver classNameTypeResolver = new ClassNameTypeResolver();
classNameTypeResolver.setClassNameExtractor((klass) -> {
// Implement Custom ClassName Extractor here
});
builder.defaultTypeResolver(classNameTypeResolver);
請參閱 GraphQlSource 章節,瞭解如何使用 Spring Boot 配置此項。
Directives
GraphQL 語言支援指令,這些指令「描述 GraphQL 文件中替代的執行時期執行和類型驗證行為」。指令類似於 Java 中的註解,但在 GraphQL 文件中的類型、欄位、片段和操作上宣告。
GraphQL Java 提供了 SchemaDirectiveWiring
合約,以協助應用程式偵測和處理指令。如需更多詳細資訊,請參閱 GraphQL Java 文件中的Schema Directives。
在 Spring GraphQL 中,您可以透過 RuntimeWiringConfigurer
註冊 SchemaDirectiveWiring
。Boot Starter 偵測此類 beans,因此您可能會看到如下內容
@Configuration
public class GraphQlConfig {
@Bean
public RuntimeWiringConfigurer runtimeWiringConfigurer() {
return builder -> builder.directiveWiring(new MySchemaDirectiveWiring());
}
}
如需指令支援的範例,請查看 Extended Validation for 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));
}
Schema 轉換
如果您想在 schema 建立後遍歷和轉換 schema,並對 schema 進行變更,您可以透過 builder.schemaResources(..).typeVisitorsToTransformSchema(..)
註冊 graphql.schema.GraphQLTypeVisitor
。請記住,這比 Schema 遍歷 更昂貴,因此通常首選遍歷而不是轉換,除非您需要進行 schema 變更。
Schema 遍歷
如果您想在 schema 建立後遍歷 schema,並可能對 GraphQLCodeRegistry
應用變更,您可以透過 builder.schemaResources(..).typeVisitors(..)
註冊 graphql.schema.GraphQLTypeVisitor
。但是,請記住,此類訪問者無法變更 schema。如果您需要變更 schema,請參閱 Schema 轉換。
Schema 映射檢查
如果查詢、變更或訂閱操作沒有 DataFetcher
,它將不會傳回任何資料,並且不會執行任何有用的操作。同樣地,既未透過 DataFetcher
註冊明確涵蓋,也未透過預設 PropertyDataFetcher
隱含涵蓋的 schema 類型欄位,後者會尋找匹配的 Class
屬性,將始終為 null
。
GraphQL Java 不執行檢查以確保每個 schema 欄位都被涵蓋,並且作為一個較低層級的程式庫,GraphQL Java 根本不知道 DataFetcher
可以傳回什麼或它依賴哪些參數,因此無法執行此類驗證。這可能會導致間隙,這些間隙可能取決於測試覆蓋率,直到執行時期才被發現,屆時用戶端可能會遇到「靜默」null
值或非空欄位錯誤。
Spring for GraphQL 中的 SelfDescribingDataFetcher
介面允許 DataFetcher
公開資訊,例如傳回類型和預期參數。所有用於控制器方法、用於 Querydsl 和用於 Query by Example 的內建 Spring DataFetcher
實作都是此介面的實作。對於註解控制器,傳回類型和預期參數基於控制器方法簽章。這使得可以在啟動時檢查 schema 映射,以確保以下事項
-
Schema 欄位具有
DataFetcher
註冊或對應的Class
屬性。 -
DataFetcher
註冊引用存在的 schema 欄位。 -
DataFetcher
參數具有匹配的 schema 欄位參數。
若要啟用 schema 檢查,請如下所示自訂 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 | 未以任何方式涵蓋的 Schema 欄位 |
2 | 對不存在的欄位的 DataFetcher 註冊 |
3 | 不存在的 DataFetcher 預期參數 |
4 | 已略過的 Schema 類型(接下來說明) |
在某些情況下,schema 類型的 Class
類型是未知的。也許 DataFetcher
未實作 SelfDescribingDataFetcher
,或者宣告的傳回類型過於通用(例如 Object
)或未知(例如 List<?>
),或者 DataFetcher
可能完全遺失。在這種情況下,schema 類型被列為已略過,因為無法驗證。對於每個略過的類型,DEBUG 訊息會說明其被略過的原因。
聯合和介面
對於聯合,檢查會迭代成員類型,並嘗試尋找對應的類別。對於介面,檢查會迭代實作類型,並尋找對應的類別。
預設情況下,可以在以下情況下開箱即用地偵測到對應的 Java 類別
-
Class
的簡單名稱與 GraphQL 聯合成員或介面實作類型名稱匹配,且Class
位於與控制器方法或控制器類別相同的套件中,該控制器方法或控制器類別映射到聯合或介面欄位。 -
Class
在 schema 的其他部分中被檢查,其中映射的欄位是具體的聯合成員或介面實作類型。 -
您已註冊一個 TypeResolver,它具有明確的
Class
到 GraphQL 類型映射。
如果以上任何一項都沒有幫助,並且 GraphQL 類型在 schema 檢查報告中被報告為已略過,您可以進行以下自訂
-
明確地將 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))
請參閱 GraphQlSource 章節,瞭解如何使用 Spring Boot 配置此項。
執行緒模型
大多數 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
值會被彙總並轉換為 List,除非請求是 GraphQL 訂閱請求,在這種情況下,傳回值仍然是 Reactive Streams Publisher
,用於串流 GraphQL 回應。
反應式 DataFetcher
可以依賴存取從傳輸層傳播的 Reactor 上下文,例如從 WebFlux 請求處理傳播的 Reactor 上下文,請參閱 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
實例在啟動時手動註冊 ThreadLocalAccessor
,該實例可透過 io.micrometer.context.ContextRegistry#getInstance()
存取。您也可以透過 java.util.ServiceLoader
機制自動註冊它。
WebFlux
反應式 DataFetcher
可以依賴存取源自 WebFlux 請求處理鏈的 Reactor 上下文。這包括 WebGraphQlInterceptor 元件新增的 Reactor 上下文。
例外
在 GraphQL Java 中,DataFetcherExceptionHandler
決定如何在回應的「errors」部分中表示來自資料擷取的例外。應用程式只能註冊一個處理常式。
Spring for GraphQL 註冊一個 DataFetcherExceptionHandler
,它提供預設處理並啟用 DataFetcherExceptionResolver
合約。應用程式可以透過 GraphQLSource
建構器註冊任意數量的解析器,這些解析器會依序執行,直到其中一個將 Exception
解析為 List<graphql.GraphQLError>
。<0xC2><0xA0>Spring Boot starter 偵測此類型的 beans。
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 引擎可能會遇到驗證或其他錯誤,這反過來會阻止請求執行。在這種情況下,回應包含一個「data」鍵,其值為 null
,以及一個或多個全域的請求層級「errors」,即沒有欄位路徑。
DataFetcherExceptionResolver
無法處理此類全域錯誤,因為它們在執行開始之前且在調用任何 DataFetcher
之前引發。應用程式可以使用傳輸層級攔截器來檢查和轉換 ExecutionResult
中的錯誤。請參閱 WebGraphQlInterceptor
下的範例。
訂閱例外
訂閱請求的 Publisher
可能會以錯誤訊號完成,在這種情況下,底層傳輸(例如 WebSocket)會傳送最終的「error」類型訊息,其中包含 GraphQL 錯誤清單。
DataFetcherExceptionResolver
無法解析來自訂閱 Publisher
的錯誤,因為資料 DataFetcher
僅在初始時建立 Publisher
。之後,傳輸會訂閱 Publisher
,然後 Publisher
可能會以錯誤完成。
應用程式可以註冊 SubscriptionExceptionResolver
,以便解析來自訂閱 Publisher
的例外,以便將這些例外解析為要傳送給用戶端的 GraphQL 錯誤。
分頁
GraphQL Cursor Connection 規範定義了一種導航大型結果集的方法,方法是每次傳回項目子集,其中每個項目都與一個游標配對,用戶端可以使用該游標請求參考項目之前或之後的更多項目。
該規範將此模式稱為「Connections」,並且名稱以 ~Connection
結尾的 schema 類型是表示分頁結果集的連接類型。所有連接類型都包含一個名為「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
參數,允許用戶端請求「first」N 個項目「after」給定的游標。同樣地,向後分頁參數的 last
和 before
參數允許請求「last」N 個項目「before」給定的游標。
規範不鼓勵同時包含 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
除了 schema 中的 連接類型 之外,您還需要等效的 Java 類型。GraphQL Java 提供了這些類型,包括通用 Connection
和 Edge
類型以及 PageInfo
。
您可以從控制器方法傳回 Connection
,但它需要樣板程式碼來調整您的底層資料分頁機制以適應 Connection
,建立游標、新增 ~Edge
包裝器和建立 PageInfo
。
Spring for GraphQL 定義了 ConnectionAdapter
合約,以將項目容器調整為 Connection
。從 DataFetcher
裝飾器調用適配器,而 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 儲存庫和 Query by Example 儲存庫使用它來解碼來自分頁請求的游標,並創建 Subrange
。
CursorEncoder
是一個相關的合約,它進一步編碼和解碼字串游標,使其對客戶端不透明。EncodingCursorStrategy
將 CursorStrategy
與 CursorEncoder
結合使用。您可以使用 Base64CursorEncoder
、NoOpEncoder
或創建您自己的編碼器。
排序
在 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 ...
}
}
啟動器宣告了一個 BatchLoaderRegistry
Bean,您可以將其注入到您的配置中(如上所示),或注入到任何組件(例如控制器)中,以便註冊批次載入函式。反過來,BatchLoaderRegistry
被注入到 DefaultExecutionGraphQlService
中,在其中它確保每個請求的 DataLoader
註冊。
預設情況下,DataLoader
名稱基於目標實體的類別名稱。這允許 @SchemaMapping
方法宣告一個帶有泛型型別的 DataLoader 引數,而無需指定名稱。但是,如有必要,可以通過 BatchLoaderRegistry
建構器自訂名稱,以及其他 DataLoaderOptions
。
要全域配置預設的 DataLoaderOptions
,以用作任何註冊的起點,您可以覆寫 Boot 的 BatchLoaderRegistry
Bean,並使用接受 Supplier<DataLoaderOptions>
的 DefaultBatchLoaderRegistry
的建構函式。
在許多情況下,當載入相關實體時,您可以使用 @BatchMapping 控制器方法,這是使用 BatchLoaderRegistry
和 DataLoader
的捷徑和替代方案。
BatchLoaderRegistry
也提供了其他重要的優點。它支援從批次載入函式和 @BatchMapping
方法存取相同的 GraphQLContext
,並確保 Context Propagation(上下文傳播) 到它們。這就是為什麼應用程式預期使用它的原因。您可以直接執行自己的 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("...");
// ...