graphql-java使用手册:part6 使用 Dataloader

原文:http://blog.mygraphql.com/wordpress/?p=110java

使用 Dataloader

使用 graphql, 你极可能会去查询图结构的数据(graph of data )
(这多是句废话).
若是用简单直接的方法去获取每一个field的数据,可能会效率很低。promise

使用 java-dataloader 能够帮助你更有效地缓存和批量化数据加载操做。
>><<dataloader会缓存全部加载过的数据,使再次使用相同数据时,不须要再加载。缓存

假设咱们用如下的 StarWars 查询。这查询了一个英雄(
hero)和他朋友的名字,和他朋友的朋友的名字。不少时候,他们有共同的朋友。网络

{
    hero {
        name
        friends {
            name
            friends {
               name
            }
        }
    }
}

下面是查询的结果。你能够看到,Han, Leia, Luke 和 R2-D2
是一群紧密的朋友。他们有不少共同的朋友。并发

[hero: [name: 'R2-D2', friends: [
        [name: 'Luke Skywalker', friends: [
                [name: 'Han Solo'], [name: 'Leia Organa'], [name: 'C-3PO'], [name: 'R2-D2']]],
        [name: 'Han Solo', friends: [
                [name: 'Luke Skywalker'], [name: 'Leia Organa'], [name: 'R2-D2']]],
        [name: 'Leia Organa', friends: [
                [name: 'Luke Skywalker'], [name: 'Han Solo'], [name: 'C-3PO'], [name: 'R2-D2']]]]]
]

一个直接的实现是为每一个人物对象(person object)调用一次 DataFetcher
去获取数据。异步

这样你须要 15 次网络调用 。即便这群有有不少相同的朋友。使用
dataloader 可让 graphql 查询更高效。ide

graphql 会批量化每一个层级的查询。 ( 如先是 hero 以后是 friends
以后是他们的 friends), Data loader 返回了一个 ”
期约(promise)”,期约会返回一个 person
object.(人物对象)。在查询的每一个层级, dataloader.dispatch()
方法均会被调用一次,以获取真实的数据。当开启了缓存功能时
(默认开启),将直接返回以前加载过的 person,而不会再发起一次查询。wordpress

上例中,共有 5 个独立的 people。但当缓存和批量化开启后,只发起了 3
次调用 batch loader 方法的查询操做。*3* 次网络或DB访问,固然比 15
次牛B多了。【译者补】fetch

若是你使用了如 java.util.concurrent.CompletableFuture.supplyAsync()
的异步程序方式 。多个field的远程加载数据就能够并发进行了。.
这可让查询更快,因一次并发了多个远程调用。ui

下面就是示例代码:

// a batch loader function that will be called with N or more keys for batch loading
BatchLoader<String, Object> characterBatchLoader = new BatchLoader<String, Object>() {
    @Override
    public CompletionStage<List<Object>> load(List<String> keys) {
        //
        // we use supplyAsync() of values here for maximum parellisation
        //
        return CompletableFuture.supplyAsync(() -> getCharacterDataViaBatchHTTPApi(keys));
    }
};

// a data loader for characters that points to the character batch loader
DataLoader<String, Object> characterDataLoader = new DataLoader<>(characterBatchLoader);

//
// use this data loader in the data fetchers associated with characters and put them into
// the graphql schema (not shown)
//
DataFetcher heroDataFetcher = new DataFetcher() {
    @Override
    public Object get(DataFetchingEnvironment environment) {
        return characterDataLoader.load("2001"); // R2D2
    }
};

DataFetcher friendsDataFetcher = new DataFetcher() {
    @Override
    public Object get(DataFetchingEnvironment environment) {
        StarWarsCharacter starWarsCharacter = environment.getSource();
        List<String> friendIds = starWarsCharacter.getFriendIds();
        return characterDataLoader.loadMany(friendIds);
    }
};

//
// DataLoaderRegistry is a place to register all data loaders in that needs to be dispatched together
// in this case there is 1 but you can have many
//
DataLoaderRegistry registry = new DataLoaderRegistry();
registry.register("character", characterDataLoader);

//
// this instrumentation implementation will dispatch all the dataloaders
// as each level fo the graphql query is executed and hence make batched objects
// available to the query and the associated DataFetchers
//
DataLoaderDispatcherInstrumentation dispatcherInstrumentation
        = new DataLoaderDispatcherInstrumentation(registry);

//
// now build your graphql object and execute queries on it.
// the data loader will be invoked via the data fetchers on the
// schema fields
//
GraphQL graphQL = GraphQL.newGraphQL(buildSchema())
        .instrumentation(dispatcherInstrumentation)
        .build();

须要注意的是,只有你使用了 DataLoaderDispatcherInstrumentation
,上面说的才会生效。由它来调用 dataLoader.dispatch() 。否则,期约(
promises ) 将不会被执行,就更不会有数据获取了。

查询范围的 Data Loaders

对于 Web
请求,请求的结果可能会因不一样用户而不一样的。若是是特定用户的数据,你必定不但愿用户A的数据,被用户B查询到。

因此 DataLoader 实例的范围很重要。这时,你须要对每一个 Request
建立一个新的 DataLoader,来保证它只在当前请求中生效。

若是你须要的是不一样请求间共享数据,因此你会但愿 DataLoader
的生命周期更长。

但如用你用请求级的 data loaders ,为每一个请求建立 GraphQL and
DataLoader 是花费不多资源的。Its the GraphQLSchema creation that can
be expensive, especially if you are using graphql SDL parsing.

i在代码中静态引用 schema 。能够是静态变量或 IoC
单件组件。但每次处理请求时,都须要建立 GraphQL 对象。

GraphQLSchema staticSchema = staticSchema_Or_MayBeFrom_IoC_Injection();

DataLoaderRegistry registry = new DataLoaderRegistry();
registry.register("character", getCharacterDataLoader());

DataLoaderDispatcherInstrumentation dispatcherInstrumentation
        = new DataLoaderDispatcherInstrumentation(registry);

GraphQL graphQL = GraphQL.newGraphQL(staticSchema)
        .instrumentation(dispatcherInstrumentation)
        .build();

graphQL.execute("{ helloworld }");

// you can now throw away the GraphQL and hence DataLoaderDispatcherInstrumentation
// and DataLoaderRegistry objects since they are really cheap to build per request
相关文章
相关标签/搜索