GraphQL-Java(二)DataFetching

GraphQL如何获取数据

每个field都有一个graphql.schema.DataFetcher与之对应

一些File会使用特定的data fetcher,这种DataFetcher知道怎么去访问数据库获取field的信息,然后大多数的dataFetcher则是简单的从内存中的object获取数据。在获取数据时,会参照field的名字and Plain Old Java Object (POJO) patterns to get the data.

所以可以像这样来声明一个type:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
type Query {
    products(match : String) : [Product]   # a list of products
}

type Product {
    id : ID
    name : String
    description : String
    cost : Float
    tax : Float
    launchDate(dateFormat : String = "dd, MMM, yyyy') : String
}

Query.products这个field有一个data fetcher,用来获取从数据库中获取Product类型的fields集合。它带有一个match参数,可以过滤出我们想要的product:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
DataFetcher productsDataFetcher = new DataFetcher<List<ProductDTO>>() {
    @Override
    public List<ProductDTO> get(DataFetchingEnvironment environment) {
        DatabaseSecurityCtx ctx = environment.getContext();

        List<ProductDTO> products;
        String match = environment.getArgument("match");
        if (match != null) {
            products = fetchProductsFromDatabaseWithMatching(ctx, match);
        } else {
            products = fetchAllProductsFromDatabase(ctx);
        }
        return products;
    }
};

每个DataFetcher相互之间传递着一个graphql.schema.DataFetchingEnvironment对象,这个对象包含正在获取的field、传递给field的参数arguments、和其他信息诸如field的类型,它父级的类型query root object或者query context object。

要注意的是,data fetcher的编码是基于context object作为一个安全程序来处理数据库的访问。这是一个基本技术来提供一个底层调用上下文。

当我们有一个ProductDTO的列表,我们不需要为每一个field都搞一个data fetcher。graphql-java 有一个聪明的graphql.schema.PropertyDataFetcher知道怎么去根据名称追踪POJO的结构。在本例中就是有个带有name的field,然后graphql.schema.PropertyDataFetcher将尝试通过POJO的public String getName()方法来获取数据。

graphql.schema.PropertyDataFetcher是默认的DataFetcher。

你仍然可以在DTO的方法中使用graphql.schema.PropertyDataFetcher来访问。这允许你在数据发送出去之前先做修改。比如,我们有个launchDate字段携带者一个可选的dateFormat参数。我们可以在ProductDTO中写个逻辑做日期时间的格式化。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class ProductDTO {

    private ID id;
    private String name;
    private String description;
    private Double cost;
    private Double tax;
    private LocalDateTime launchDate;

    // ...

    public String getName() {
        return name;
    }

    // ...

    public String getLaunchDate(DataFetchingEnvironment environment) {
        String dateFormat = environment.getArgument("dateFormat");
        return yodaTimeFormatter(launchDate,dateFormat);
    }
}

定制PropertyDataFetcher

像之前讲的一样,graphql.schema.PropertyDataFetcher是默认的data fetcher,并且它在获取数据时使用标准结构。

它支持POJO和map。假设有个field叫fieldX,它将会寻找一个POJO也叫做fieldX,或者在Map中去找一个key也叫做fieldX

然而,你在定义的schema与POJO有一点不通,比如Product.description对应着POJO中的getDesc().

你可以在SDL中使用@fetch注解来做匹配:

1
2
3
4
5
6
7
8
9
directive @fetch(from : String!) on FIELD_DEFINITION

type Product {
    id : ID
    name : String
    description : String @fetch(from:"desc")
    cost : Float
    tax : Float
}

这将告诉graphql.schema.PropertyDataFetcher在获取数据时应该使用名为desc的属性来对应SDL中的description字段。

如果你不用SDL,而是喜欢手写代码,那么可以直接指定Data Fetcher:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
GraphQLFieldDefinition descriptionField = GraphQLFieldDefinition.newFieldDefinition()
                .name("description")
                .type(Scalars.GraphQLString)
                .build();

        GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry()
                .dataFetcher(
                        coordinates("ObjectType", "description"),
                        PropertyDataFetcher.fetching("desc"))
                .build();

DataFetchingEnvironment中有意思的部分

每一个data fetcher都传递了一个graphql.schema.DataFetchingEnvironment对象。它知道我们正在获取什么东西,并且知道提供了哪些参数。

ExecutionStepInfo好玩的地方

一个graphql query在执行时会生成一个call tree,就是「调用树」。graphql.execution.ExecutionStepInfo.getParentTypeInfo允许你向上导航,看到是什么类型的field被带到了当前的执行过程。

在整个执行期间,就形成了一个树形path,graphql.execution.ExecutionStepInfo.getPath方法可以返回这个树形path的描述。这在日志打印和调试query时比较有用。而且可以查看到非空类型的名称和折叠后的列表。

一句话总结就是,主要用于调试和排错。

DataFetchingFieldSelectionSet有趣的地方

想象一下有个query就像这样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
query {
    products {
        # the fields below represent the selection set
        name
        description
        sellingLocations {
            state
        }
    }
}

Product的所有子field,对于Product来讲就是selection fields。当我们想要知道哪些子级field正在被请求时非常有用。举例来讲,我们的POJO中可能有很多属性,数据库表中的字段也与之一一对应。但是我们跑个graphql查询时可能不需要返回全部的字段,我们只要我们需要的字段就行了,此时data fetcher去查数据库时也不需要查出所有的列,也是按需获取就行了。那怎么在代码里感知到我们具体请求的哪些字段呢?就是靠这个DataFetchingFieldSelectionSet

比如上例中我们请求了sellingLocations字段,或者我们使用更高效的查询:同时查出productssellingLocations