graphql-java-kickstart/graphql-java-servlet

Dataloader bug (JavaEE+MyBatis)

iteaky opened this issue · 0 comments

I have graphql in my production, and it's forbidden to share the code. I'm using graphql-java-servlet, as ORM I use MyBatis.

com.graphql-java-kickstart graphql-java-servlet 9.2.0 After enabling BatchLoading I found that graphQL DataLoader is confuses places of batch-requested Entities. Also it's easy to check if take a look in futureCacheMap, you will find that Key(Id) And Value(Entity) have different id's.

After debbuging, I did't found the way how graphQL resolve Entites after batchLoading (1000 psc). So I decided that I should have some ordering, so I implemented it, but It didn't resolve the issue.

For example: I Have Parent.class who Have Childs inside.

class Parent {
private Long id;
private List<Long> childsIds;
}

I have ChildDataloader

   private BatchLoader<Long, Child> buildBatchLoader() {
        return list -> > CompletableFuture.supplyAsync(() ->
                                  childService.findByIds(list));
                        
    }

    private DataLoader<Long, Child> buildDataLoader(BatchLoader batchLoader) {
        DataLoaderOptions options = DataLoaderOptions.newOptions();
        options.setMaxBatchSize(1000);
        return new DataLoader<Long, Child>(batchLoader, options);
    }
}

I have ChildsFetcher where I call dataLoader.loadMany()

public class ChildsFetcher implements DataFetcher<CompletableFuture<List<Child>>>{

    private static final String PK_FIELD_NAME = "childsIds";

    @Override
    public CompletableFuture<List<LoadDefinitionDTO>> get(DataFetchingEnvironment environment) {
        GraphQLContext context = environment.getContext();
        DataLoaderRegistry dataLoaderRegistry = context.getDataLoaderRegistry().orElseThrow(
                () -> new DalException("there was no dataLoaderRegistry in context", Response.Status.INTERNAL_SERVER_ERROR)
        );
        List<Long> childsIds = getParentFieldValue(environment, LOAD_DEFINITION_PK_FIELD_NAME, List.class);

        DataLoader<Long, Child> childDataLoader = dataLoaderRegistry.getDataLoader("childDataLoader");
        return childDataLoader.loadMany(childsIds)
    }
}

For Example I have 2 Parents with 3 child each.

parents: [
 {
   "id": 1
   "childIds": {1,3,5}
 },
 {
   "id": 2
   "childIds": {2,4,6}
}

As a result in fetcher I will have 2 requests:

  1. childDataLoader.loadMany({1,3,5})

  2. childDataLoader.loadMany({2,4,6})

But in Dataloader it will be only one (as expected), but look at the order of the ids (I cannot take control on it):

childService.findByIds({1,2,3,4,5,6})

And in output I will receive:


"data": {
    "parents": [
      { 
        "id": 1,
        "childs": [
          {
            "id": 1,
          },
          {
            "id": 2,
           },
          {
            "id": 3,
           }
       },
      { 
        "id": 2,
        "childs": [
          {
            "id": 4,
          },
          {
            "id": 5,
           },
          {
            "id": 6,
           }
       }
      ]
     }

link to stackoverflow