GraphQL Java - Data Fetching


一、graphql如何獲取數據

每個graphql中定義的字段都有一個相關聯的graphql.schema.DataFetcher。

有些字段使用自定義的data fetcher代碼,用於訪問數據庫並從數據庫中獲取字段信息。而大多數字段僅使用字段名稱,在內存中的Map對象或或普通的Java對象(POJO)中獲取數據。

在其他的GraphQL 實現當中,Data Fetcher會有時稱為resolvers。

假設一個類型定義如下:

    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字段有一個Data Fetcher,Product類型中的每個字段也一樣。

Query.products字段的Data Fetcher可能非常復雜,包含從數據庫中讀取Product對象的操作。它使用一個可選的match參數,進而可以對products結果中的對象進行過濾。

其示例如下:

        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對象,該對象包含正在fetch的field(字段),提供給字段的參數argument以及其他信息,例如字段的類型,父類型,查詢根對象或查詢上下文對象。

在上下文調用中,可以使用上下文對象,在訪問數據庫時提供安全性驗證。

有了ProductDTO對象列表,通常不需要在每個字段上使用專門的數據獲取器。 graphql-java提供了graphql.schema.PropertyDataFetcher,它可以根據字段名稱,遵循POJO模式獲取字段的值。 在上面的示例中,有一個name字段,PropertyDataFetcher將嘗試查找POJO中的String getName()方法來獲取數據。

graphql.schema.PropertyDataFetcher是默認的字段data fetcher。

也可以通過訪問DTO方法中的graphql.schema.DataFetchingEnvironment,調整字段的返回值。

例如,上面我們有一個launchDate字段,它接受一個可選的dateFormat參數。 我們可以讓ProductDTO具有將此日期格式應用於所需格式的邏輯。

    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是graphql-java中字段的默認數據獲取器,它將使用標准模式來獲取對象字段值。

它以Java習慣的方式支持POJO對象方法和Map映射值獲取。 默認情況下,假定對於graphql字段fieldX,它可以在Map類型對象中尋找key為fieldX的值,或POJO對象中名為fieldX的屬性值。

GraphQL Schema中的字段明明和運行時對象的命名之間可能存在細微差別。 例如,假設Product.description在運行時實際調用的java方法為getDesc()。

如果使用SDL指定schema,則可以使用@fetch指令指示此重新映射。

    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在獲取名為description的字段數據時會使用屬性名desc。

如果使用代碼手動構造schema,那么只需要在代碼中直接進行指定即可。

        GraphQLFieldDefinition descriptionField = GraphQLFieldDefinition.newFieldDefinition()
                .name("description")
                .type(Scalars.GraphQLString)
                .build();

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

三、DataFetchingEnvironment

每個數據獲取器都傳遞一個graphql.schema.DataFetchingEnvironment對象,使用該對象可以獲取到當前正在獲取的內容,以及相關的上下文信息。

  • T getSource():source對象用於獲取field的信息。 它是獲取parent field時請求到的返回值對象。 通常,它是存放於內存中的DTO對象,可以通過getter方法獲取field的值。 在一些復雜場景中,可以通過該對象來獲取當前請求的field相關的更詳細的信息。 在對graphql請求樹上的field發起請求時,每個請求field得到的返回值,都會作為該field下的子field的source對象。
  • T getRoot(): root對象是graphql查詢中的初始數據。對於graphql查詢樹的根查詢field,其source對象和root對象是同一個。 root對象在查詢進行期間不可修改。
  • Map<String, Object> getArguments():表示在請求一個field時所需傳遞的變量參數。表示已在字段上提供的參數以及從傳遞的變量,GraphQL內部的AST常量或默認參數值。 在發起請求是,可以在field字段上附加參數arguments,來改變請求fields時的內部邏輯。
  • T getContext():context對象是初次執行查詢時所設置的對象。在GraphQL查詢的整個周期內,該對象保持唯一。 context對象上可以是任意值,通常用於為每個data fetcher提供獲取field相應值所需的上下文信息。 例如,當前用戶憑證或數據庫連接參數可以附加在context對象中,以便data fetcher進行業務層調用。 在設計GraphQL的接口是,關鍵的權衡點之一就是如何使用context對象。 有些情況下,設計人員會依賴其他框架保存相關上下文信息,這種情況下就不需要使用GraphQL中的context對象。
  • ExecutionStepInfo getExecutionStepInfo():ExecutionStepInfo對象在GraphQL查詢進行時創建,它包含了關於field的所有類型信息。
  • DataFetchingFieldSelectionSet getSelectionSet():SelectionSet表示在GraphQL查詢樹上,當前正在查詢的field字段下的所有子查詢字段。
  • ExecutionId getExecutionId():GraphQL的每一次查詢都有一個唯一的執行id,可以用於日志記錄當中。

3.1 ExecutionStepInfo

GraphQL 查詢的過程當中,會創建一個field及其type的調用樹。
通過調用ExecutionStepInfo.getParentTypeInfo,你可以在調用樹的當前節點上,向上一層進行遍歷,並查看到有哪些父field或type,觸發了當前field的執行。

ExecutionStepInfo.getPath方法返回了一個樹節點的path的表示。在日志中記錄當前遍歷的節點的path對於調試GraphQL的執行而言非常有用。

還有一些輔助方法,可以對使用@Nonnull或List包裝后的類型進行拆解,獲取其內部的原始類型。(例如:@NonNull Person person或List ,通過調用輔助方法, 可以獲取到原始類型Person)

3.2 DataFetchingFieldSelectionSet

對於如下查詢

    query {
        products {
            # the fields below represent the selection set
            name
            description
            sellingLocations {
                state
            }
        }
    }

products字段的子field(字段)表示該字段的selectionSet(選擇集)。 提前獲取到當前field 的子field在某些場景下非常有用,例如:可以通過提前獲取當前field下的子field,來優化數據庫訪問的執行。例如,只請求當前field下的子選擇集,而非查詢當前表的所有字段,提升數據庫訪問性能。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM