I am facing the graphql n+1
issue in one of my applications where I am using graphql with restful webservice in java. I am using schemagen-graphql
with spring-data-jpa
for connecting to my oracle db.
I saw so many posts but most of the answer were graphql-node.js
implementation. Anything related to java will be good.
I don't know much about schemagen-graphql
and whether it matters here at all but, in general, there's currently 3 options with graphql-java
:
DataFetcher
). You can look ahead to see exactly which sub-fields are requested by examining the DataFetchingFieldSelectionSet
.
If the prefetched data doesn't naturally fit into the result object, you can store it in the LocalContext
and make use of it in when resolving the child field value. This approach is nicely explained in this blog post.graphql.execution
) or the experimental (if use the classes under graphql.execution.nextgen
) approach of annotating your data fetcher with @Batched
and using BatchedExecutionStrategy
.BatchedExecutionStrategy
(the legacy version doesn't fully respect the specification when it comes to handling nulls).DataFetcher#get
method with @Batched
DataFetchingEnvironment#getSource
will always return a list of source objects (instead of one).List<Article> articles = DataFetchingEnvironment.getSource(); //source is a list List<Long> authorIds = articles.stream.map(article -> article.getAuthodId()).collect(Collectors.toList()); //fetch all the authors in one go SELECT * FROM Author WHERE author_id IN (authorIds)
DataLoader
to fetch your data instead of fetching it directly. The idea is very similar to the original data-loader JavaScript library. Works only with the default AsyncExecutionStrategy
and only for queries (so don't expect it to work for mutations and subscriptions).You can provide an instance of DataLoaderRegistry
in ExecutionInput
, and you normally want to re-create the data loaders and the registry on each request (batch loaders can be shared if they're stateless):
DataLoaderRegistry loaders = ...; //initialize your loaders, usually per request
graphQL.execute(ExecutionInput.newExecutionInput()
.query(operation)
.dataLoaderRegistry(loaders) //add the registry to input
.build());
Then in your DataFetcher
you can always get to the DataLoader
you need via DataFetchingEnvironment#getDataLoader(String dataLoaderName)
:
return env.getDataLoader("authors").load(article.getAuthorId());
As a sidenote, you may want to check out my own library for generating the GraphQL API from Java, GraphQL-SPQR. Here's a minimal demonstration of using @Batched
with it.
As for the DataLoader, the logic is the same as above, and you get to the DataLoaderRegistry
via @GraphQLEnvironment
annotation:
@GrapgQLQuery
public CompletableFuture<Author> author(@GraphQLContext Article article, @GraphQLEnvironment ResolutionEnvironment env) {
return env.dataFetchingEnvironment.getDataLoader("authors").load(article.getAuthorId())
}
I'll likely also add a specialized annotation like @DataLoader("authors")
for injecting the loaders directly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With