1

我正在使用 GrapgQL 和 Java。我需要提取所有属于特定父母的孩子。我使用了以下方式,但它只会获取父级并且不会获取任何子级。

schema {
query: Query
}

type LearningResource{
id: ID
name: String
type: String
children: [LearningResource]
}

type Query {
fetchLearningResource: LearningResource
}

@Component

公共类 LearningResourceDataFetcher 实现 DataFetcher{

@Override
public LearningResource get(DataFetchingEnvironment dataFetchingEnvironment) {


    LearningResource lr3 = new LearningResource();
    lr3.setId("id-03");
    lr3.setName("Resource-3");
    lr3.setType("Book");

    LearningResource lr2 = new LearningResource();
    lr2.setId("id-02");
    lr2.setName("Resource-2");
    lr2.setType("Paper");


    LearningResource lr1 = new LearningResource();
    lr1.setId("id-01");
    lr1.setName("Resource-1");
    lr1.setType("Paper");

    List<LearningResource> learningResources = new ArrayList<>();
    learningResources.add(lr2);
    learningResources.add(lr3);

    learningResource1.setChildren(learningResources);

    return lr1;
}
}


return RuntimeWiring.newRuntimeWiring().type("Query", typeWiring -> typeWiring.dataFetcher("fetchLearningResource", learningResourceDataFetcher)).build();

我的控制器端点

@RequestMapping(value = "/queryType", method = RequestMethod.POST)
public ResponseEntity query(@RequestBody String query) {
    System.out.println(query);
    ExecutionResult result = graphQL.execute(query);
    System.out.println(result.getErrors());
    System.out.println(result.getData().toString());
    return ResponseEntity.ok(result.getData());
}

我的要求如下

{
 fetchLearningResource
 {
  name
 }
}

任何人都可以帮我解决这个问题吗?

4

1 回答 1

1

因为我在现实生活中经常被问到这个问题,所以我会在这里详细回答,这样人们就可以更轻松地进行谷歌搜索(我有一些东西要指出)。

如评论中所述,每个级别的选择必须是明确的,并且没有无限递归查询的概念,例如将节点下的所有内容都放到底部(或递归地将此父节点的所有子节点放到底部)。

原因主要是允许这样的查询很容易让你陷入危险的境地:用户可以轻松地从服务器请求整个对象图!对于任何重要的数据大小,这都会立即杀死服务器并使网络饱和。此外,一旦遇到递归关系会发生什么?

不过,您可以在这里使用一个半控制的逃生舱口。如果您需要所有内容的范围有限(确实应该如此),您可以将特定查询的输出类型映射为(复杂)标量。

在您的情况下,这意味着映射LearningResource为标量。然后,fetchLearningResource将有效地返回一个JSON blob,其中 blob 恰好是递归的所有孩子及其孩子。一旦达到标量字段,查询解析就不会下降得更深,因为标量是叶节点,因此它不能继续逐级解析子节点。这意味着您必须自己递归地一次性获取所有内容,因为 GraphQL 引擎在这里帮不了你。这也意味着子选择变得不可能(因为标量不能有子选择 - 同样,它们是叶节点),因此客户端总是会从每个子节点中获取所有子节点和所有字段。如果您仍然需要在某些情况下限制选择的能力,您可以公开 2 个不同的查询,例如fetchLearningResourcefetchAllLearningResources,前者将按现在的样子映射,后者将按说明返回标量。

graphql-java ExtendedScalars项目提供了一个对象标量实现。

架构可能如下所示:

schema {
    query: Query
}

scalar Object

type Query {
    fetchLearningResource: Object
}

您将使用上面的方法来生成标量实现:

RuntimeWiring.newRuntimeWiring()
    .scalar(ExtendedScalars.Object) //register the scalar impl
    .type("Query", typeWiring -> typeWiring.dataFetcher("fetchLearningResource", learningResourceDataFetcher)).build();

根据您处理此查询结果的方式,DataFetcherforfetchLearningResource可能需要在返回客户端之前将结果对象转换为 map-of-maps(类似 JSON 的对象)。如果你只是简单地对结果进行 JSON 序列化,你可能会跳过这个。请注意,您在这里避开了所有安全机制,并且必须注意不要产生巨大的结果。通过扩展,如果您在很多地方都需要它,那么您很可能使用了完全错误的技术来解决您的问题。

我自己没有用你的代码测试过这个,所以我可能跳过了一些重要的东西,但这应该足以让你(或任何人在谷歌上搜索)走上正确的轨道(如果你确定这正确的轨道)。

更新:我看到有人实现了一个自定义Instrumentation,它在解析查询后立即重写查询,如果尚未选择任何字段,则递归地将所有字段添加到选择集中。这有效地允许他们隐式选择所有内容。在 graphql-java v11 和之前的版本中,您可以改变解析的查询(由Document类表示),但从 v12 开始,它不再可能,但仪器反过来获得了Document通过新instrumentDocument方法显式替换的能力。当然,这仅在您的架构无法被利用或您完全控制客户端时才有意义所以没有危险。您也可以仅对某些类型有选择地执行此操作,但使用起来会非常混乱。

于 2018-02-18T21:53:08.937 回答