【问题标题】:DataLoader batch loading bug (MyBatis, JavaEE)DataLoader 批量加载错误(MyBatis、JavaEE)
【发布时间】:2020-12-17 04:39:54
【问题描述】:

我的生产中有graphql,禁止分享代码。我用的是graphql-java-servlet,作为ORM我用的是MyBatis。

<dependency>
     <groupId>com.graphql-java-kickstart</groupId>
     <artifactId>graphql-java-servlet</artifactId>
     <version>9.2.0</version>
</dependency>

启用 BatchLoading 后,我发现 graphQL DataLoader 会混淆批量请求实体的位置。看futureCacheMap也很容易,你会发现Key(Id)和Value(Entity)有不同的id。

调试后,我没有找到graphQL在batchLoading(1000 psc)后如何解析实体的方式。所以我决定我应该有一些排序,所以我实现了它,但它并没有解决问题。

例如: 我有 Parent.class,里面有 Childs。

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

我有 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);
    }
}

我有 ChildsFetcher 我调用 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, PK_FIELD_NAME , List.class);

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

例如,我有 2 个父母,每个人有 3 个孩子。

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

作为 fetcher 的结果,我将有 2 个请求:

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

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

在 Dataloader 中它将只有一个(如预期的那样),但请查看 id 的顺序(我无法控制它):

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

在输出中我会收到:

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

【问题讨论】:

  • 通常根据 input-ids 的顺序对 childService#findByIds 的结果进行排序应该可以解决问题。你确定 input-id 列表的顺序已经搞砸了吗?您是否使用调试器进行了验证?
  • 是的,我用调试器验证了它,现在对 sql 响应的排序很有帮助
  • 如果我尝试像这样进行 smt:childService.findByIds(list).thenApply(listEntityes -&gt; { long count = listEntityes.stream() .filter(entitye -&gt; !listIds.contains(.(entitye .getId()))).count(); if (count &gt; 0) { throw new Exception("Different sql request and response"); } return it; }); 我会得到一个异常
  • 如果在 CompletableFuture.supplyAsync(() -> childService.findByIds(list)); 中设置断点,列表的顺序是否仍然正确?
  • 我想我找到了解决方案,我也有一些遗漏的排序,这破坏了 DataLoader 的顺序。会检查,如果是,我会改变我的问题并提供答案

标签: java graphql jax-rs mybatis java-ee-7


【解决方案1】:

您的回答顺序必须与请求的顺序相同, 如果 ORM 对 sql 响应进行了排序,则在从 ORM 获得响应后将其返回 DataLoader 即可,例如:

 private BatchLoader<Long, Child> buildBatchLoader() {
   return list -> CompletableFuture.supplyAsync(() -> 
                   childService.findByIds(list).stream()
                     .sorted(Comparator.comparingLong(entity -> 
                             list.indexOf(entity.getId())))
                     .collect(Collectors.toList()));
 };   
    
         
                     

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-10-28
    • 2015-05-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-26
    • 2014-05-14
    相关资源
    最近更新 更多