原文:http://blog.mygraphql.com/wordpress/?p=110java
使用 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 ) 將不會被執行,就更不會有數據獲取了。
對於 Web
請求,請求的結果可能會因不一樣用戶而不一樣的。若是是特定用戶的數據,你必定不但願用戶A的數據,被用戶B查詢到。
因此 DataLoader 實例的範圍很重要。這時,你須要對每一個 Request
建立一個新的 DataLoader,來保證它只在當前請求中生效。
若是你須要的是不一樣請求間共享數據,因此你會但願 DataLoader
的生命週期更長。
但如用你用請求級的 data loaders ,爲每一個請求建立 GraphQL
andDataLoader
是花費不多資源的。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