@@ -346,44 +346,108 @@ default it is marked as `INTERNAL_ERROR`.
346
346
347
347
348
348
[[execution-batching]]
349
- === Batching
350
-
351
- Given a `Book` and its `Author`, we can create one `DataFetcher` for books and another
352
- for the author of a book. This means books and authors aren't automatically loaded
353
- together, which enables queries to select the subset of data they need. However, loading
354
- multiple books, results in loading each author individually, and this is a performance
355
- issue known as the N+1 select problem.
356
-
357
- GraphQL Java provides a
358
- https://www.graphql-java.com/documentation/v16/batching/[batching feature] that allows
359
- related entities, such as the authors for all books, to be loaded together. This is how
360
- the underlying mechanism works in GraphQL Java:
361
-
362
- - For each request, an application can register a batch loading function as a
363
- `DataLoader` in the `DataLoaderRegistry` to assist with loading instances of a given
364
- entity, such as `Author` from a set of unique keys.
365
- - A `DataFetcher` can access the `DataLoader` for the entity and use it to load entity
366
- instances; for example the author `DataFetcher` obtains the authorId from the `Book`
367
- parent object, and uses it to load the `Author`.
368
- - `DataLoader` does not load the entity immediately but rather returns a future, and
369
- defers until it is ready to batch load all related entities as one.
370
- - `DataLoader` additionally maintains a cache of previously loaded entities that can
371
- further improve efficiency when the same entity is in multiple places of the response.
372
-
373
- Spring for GraphQL provides:
374
-
375
- - `BatchLoaderRegistry` that accepts and stores registrations of batch loading functions;
376
- This is used in `ExecutionGraphQlService` to make `DataLoader` registrations per request.
377
- - <<controllers-schema-mapping-data-loader,DataLoader argument>> for `@SchemaMapping`
378
- methods to access the `DataLoader` for the field type.
379
- - <<controllers-batch-mapping,@BatchMapping>> data controller methods that provide a
380
- shortcut and avoid the need to use `DataLoader` directly.
349
+ === Batch Loading
350
+
351
+ Given a `Book` and its `Author`, we can create one `DataFetcher` for a book and another
352
+ for its author. This allows selecting books with or without authors, but it means books
353
+ and authors aren't loaded together, which is especially inefficient when querying multiple
354
+ books as the author for each book is loaded individually. This is known as the N+1 select
355
+ problem.
356
+
357
+
358
+ [[execution-batching-dataloader]]
359
+ ==== `DataLoader`
360
+
361
+ GraphQL Java provides a `DataLoader` mechanism for batch loading of related entities.
362
+ You can find the full details in the
363
+ https://www.graphql-java.com/documentation/v16/batching/[GraphQL Java docs]. Below is a
364
+ summary of how it works:
365
+
366
+ 1. Register ``DataLoader``'s in the `DataLoaderRegistry` that can load entities, given unique keys.
367
+ 2. ``DataFetcher``'s can access ``DataLoader``'s and use them to load entities by id.
368
+ 3. A `DataLoader` defers loading by returning a future so it can be done in a batch.
369
+ 4. ``DataLoader``'s maintain a per request cache of loaded entities that can further
370
+ improve efficiency.
371
+
372
+
373
+ [[execution-batching-batch-loader-registry]]
374
+ ==== `BatchLoaderRegistry`
375
+
376
+ The complete batching loading mechanism in GraphQL Java requires implementing one of
377
+ several `BatchLoader` interface, then wrapping and registering those as ``DataLoader``s
378
+ with a name in the `DataLoaderRegistry`.
379
+
380
+ The API in Spring GraphQL is slightly different. For registration, there is only one,
381
+ central `BatchLoaderRegistry` exposing factory methods and a builder to create and
382
+ register any number of batch loading functions:
383
+
384
+ [source,java,indent=0,subs="verbatim,quotes"]
385
+ ----
386
+ @Configuration
387
+ public class MyConfig {
388
+
389
+ public MyConfig(BatchLoaderRegistry registry) {
390
+
391
+ registry.forTypePair(Long.class, Author.class).registerMappedBatchLoader((authorIds, env) -> {
392
+ // return Mono<Map<Long, Author>
393
+ });
394
+
395
+ // more registrations ...
396
+ }
397
+
398
+ }
399
+ ----
381
400
382
401
The Spring Boot starter declares a
383
- <<boot-graphql-batch-loader-registry,BatchLoaderRegistry bean>>, so that applications can
384
- simply autowire the registry into their controllers in order to register batch loading
385
- functions for entities.
402
+ <<boot-graphql-batch-loader-registry,BatchLoaderRegistry bean>> so you can inject it into
403
+ your configuration, as shown above, or into any component such as a controller in order
404
+ register batch loading functions. In turn the `BatchLoaderRegistry` is injected into
405
+ `ExecutionGraphQlService` where it ensures `DataLoader` registrations per request.
406
+
407
+ By default, the `DataLoader` name is based on the class name of the target entity.
408
+ This allows an `@SchemaMapping` method to declare a
409
+ <<controllers-schema-mapping-data-loader,DataLoader argument>> with a generic type, and
410
+ without the need for specifying a name. The name, however, can be customized through the
411
+ `BatchLoaderRegistry` builder, if necessary, along with other `DataLoader` options.
412
+
413
+ For many cases, when loading related entities, you can use
414
+ <<controllers-batch-mapping,@BatchMapping>> controller methods, which are a shortcut
415
+ for and replace the need to use `BatchLoaderRegistry` and `DataLoader` directly.
416
+ s
417
+ `BatchLoaderRegistry` provides other important benefits too. It supports access to
418
+ the same `GraphQLContext` from batch loading functions and from `@BatchMapping` methods,
419
+ as well as ensures <<execution-context>> to them. This is why applications are expected
420
+ to use it. It is possible to perform your own `DataLoader` registrations directly but
421
+ such registrations would forgo the above benefits.
422
+
423
+
424
+ [[execution-batching-testing]]
425
+ ==== Testing Batch Loading
426
+
427
+ Start by having `BatchLoaderRegistry` perform registrations on a `DataLoaderRegistry`:
428
+
429
+ [source,java,indent=0,subs="verbatim,quotes"]
430
+ ----
431
+ BatchLoaderRegistry batchLoaderRegistry = new DefaultBatchLoaderRegistry();
432
+ // perform registrations...
386
433
434
+ DataLoaderRegistry dataLoaderRegistry = DataLoaderRegistry.newRegistry().build();
435
+ batchLoaderRegistry.registerDataLoaders(dataLoaderRegistry, graphQLContext);
436
+ ----
437
+
438
+ Now you can access and test individual ``DataLoader``'s as follows:
439
+
440
+ [source,java,indent=0,subs="verbatim,quotes"]
441
+ ----
442
+ DataLoader<Long, Book> loader = dataLoaderRegistry.getDataLoader(Book.class.getName());
443
+ loader.load(1L);
444
+ loader.loadMany(Arrays.asList(2L, 3L));
445
+ List<Book> books = loader.dispatchAndJoin(); // actual loading
446
+
447
+ assertThat(books).hasSize(3);
448
+ assertThat(books.get(0).getName()).isEqualTo("...");
449
+ // ...
450
+ ----
387
451
388
452
389
453
@@ -987,12 +1051,9 @@ to locate it in the `DataLoaderRegistry`. As a fallback, the `DataLoader` method
987
1051
resolver will also try the method argument name as the key but typically that should not
988
1052
be necessary.
989
1053
990
- [TIP]
991
- ====
992
- For straight-forward cases where the `@SchemaMapping` simply delegates to a `DataLoader`,
993
- you can reduce boilerplate by using a <<controllers-batch-mapping,@BatchMapping>> method
994
- instead.
995
- ====
1054
+ Note that for many cases with loading related entities, where the `@SchemaMapping` simply
1055
+ delegates to a `DataLoader`, you can reduce boilerplate by using a
1056
+ <<controllers-batch-mapping,@BatchMapping>> method as described in the next section.
996
1057
997
1058
998
1059
0 commit comments