GraphQL快速入門-JAVA

GraphQL

GraphQL是一種描述請求數據的查詢語言,一種規範,通常用於前後端數據交互,GraphQL的出現解決了RESTful請求造成的資源浪費,

如我需要查詢用戶的班級Id:

#請求 GET http://127.0.0.1/user/1001
#響應   {"id":10037,"name":"ls","classId":"10341"}
 
 
響應結果響應了多餘的數據,id,name,這其實算是一種資源的浪費,儘管微不足道
 
 
 

再比如我需要查找用戶班級Id,跟班級名稱

#請求 GET http://127.0.0.1/user/1001
#響應   {"id":10037,"name":"ls","classId":"10341"}
 
#請求 GET http://127.0.0.1/class/10341
#響應   {"classId":10037,"name":"計算機一班",.....}

 

查詢用戶以及他的身份證信息,需要進行2次查詢才能夠完成,這樣對於前端等接口的使用方是很不友好的,試想一下,如果查詢信息有10個,是不是要發起10次請求才能完成?
 
 

概括

簡單的說,GraphQL 是一種描述請求數據方法的語法,通常用於客戶端從服務端加載數據。GraphQL 有以下三個主要特徵:

  • 它允許客戶端指定具體所需的數據。
  • 它讓從多個數據源彙總取數據變得更簡單。
  • 它使用了類型系統來描述數據。

 

GraphQL 提出的解決方案

Facebook 提出了一個概念很簡單的解決方案:不再使用多個“愚蠢”的節點,而是換成用一個“聰明”的節點來進行復雜的查詢,將數據按照客戶端的要求傳回。

實際上,GraphQL 層處於客戶端與一個或多個數據源之間,它接收客戶端的請求然後根據你的設定取出需要的數據。

理論上,一個 GraphQL API 主要由三個部分組成:schema(類型)queries(查詢) 以及 resolvers(解析器)

 

GraphQL按需索取數據,避免浪費

 

 

可以看出請求中只有name屬性,響應結果中也只包含name屬性,如果請求中添加height,mass屬性,那麼結果也會根據請求返回
 
 
GraphQL一次請求多個數據

可以看到,一次請求,不僅查詢到了hero數據,而且還查詢到了friends數據。節省了網絡請求次數。

 

GraphQL查詢規範

GraphQL定義了一套規範,用來描述語法定義,具體參考:http://graphql.cn/learn/queries/

字段(Fields

GraphQL的查詢中,請求結構中包含了所預期結果的結構,這個就是字段。並且響應的結構和請求結構基本一致,這是GraphQL的一個特性,這樣就可以讓請求發起者很清楚的知道自己想要什麼。

 

參數(Arguments
在查詢數據時,離不開傳遞參數,在GraphQL的查詢中,也是可以傳遞參數的,語法:(參數名:參數值)

別名(Aliases)

如果你眼睛夠銳利,你可能已經發現,即便結果中的字段與查詢中的字段能夠匹配,但是因爲他們並不包含參數,你就沒法通過不同參數來查詢相同字段J(SON語法,同級不能出現相同name的值)。這便是爲何你需要別名 —— 這可以讓你重命名結果中的字段爲任意你想到的名字。

 

 

GraphQLSchema 和類型規範

Schema 是用於定義數據結構的,比如說,User對象中有哪些屬性,對象與對象之間是什麼關係等。

schema { #定義查詢
query: UserQuery
}
type UserQuery { #定義查詢的類型
user(id:ID) : User #指定對象以及參數類型
}
type User { #定義對象
id:ID! # !表示該屬性是非空項
name:String
age:Int
}

標量類型

我們知道這些字段沒有任何次級字段 —— 因爲讓它們是查詢的葉子節點。

GraphQL 自帶一組默認標量類型:

  • Int:有符號 32 位整數。
  • Float:有符號雙精度浮點值。
  • String:UTF‐8 字符序列。
  • Booleantrue 或者 false
  • ID:ID 標量類型表示一個唯一標識符,通常用以重新獲取對象或者作爲緩存中的鍵。ID 類型使用和 String 一樣的方式序列化;然而將其定義爲 ID 意味着並不需要人類可讀型。
規範中定義的這5種類型,顯然是不能滿足需求的,所以在各種語言實現中,都有對類型進行了擴充,也就是GraphQL支持自定義類型,比如在graphql-java實現中增加了:LongByte等。
 
列表和非空
對象類型、標量以及枚舉是 GraphQL 中你唯一可以定義的類型種類。但是當你在 schema 的其他部分使用這些類型時,或者在你的查詢變量聲明處使用時,你可以給它們應用額外的類型修飾符來影響這些值的驗證。我們先來看一個例子:
type Character {
  name: String!
  appearsIn: [Episode]!
}

此處我們使用了一個 String 類型,並通過在類型名後面添加一個感嘆號!將其標註爲非空。這表示我們的服務器對於這個字段,總是會返回一個非空值,如果它結果得到了一個空值,那麼事實上將會觸發一個 GraphQL 執行錯誤,以讓客戶端知道發生了錯誤。

非空類型修飾符也可以用於定義字段上的參數,如果這個參數上傳遞了一個空值(不管通過 GraphQL 字符串還是變量),那麼會導致服務器返回一個驗證錯誤。

 

解析器Resolvers

GraphQL 服務端並不知道要對一個即將到來的查詢做什麼處理,除非你使用 resolver 來告訴他

resolver是決定schemas中的field該如何執行的函數。

 

GraphQLJava實現
 
官網:https://www.graphql-java.com
 

 導入Maven依賴:

        <dependency>
            <groupId>com.graphql-java</groupId>
            <artifactId>graphql-java</artifactId>
            <version>11.0</version>
        </dependency>

<!--io操作工具包-->
 ``````<dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-io</artifactId>
            <version>1.3.2</version>
        </dependency>

說明:graphql-java包並沒有發佈到maven中央倉庫,需要配置第三方倉庫才能使用。setting.xml文件裏進行配置:

<profile>
		<id>bintray</id>
		<repositories>
			<repository>
				<id>bintray</id>
				<url>http://dl.bintray.com/andimarek/graphql-java</url>
				<releases>
				<enabled>true</enabled>
				</releases>
				<snapshots>
				<enabled>false</enabled>
				</snapshots>
				</repository>
			</repositories>
		<pluginRepositories>
		<pluginRepository>
			<id>bintray</id>
			<url>http://dl.bintray.com/andimarek/graphql-java</url>
			<releases>
			<enabled>true</enabled>
			</releases>
			<snapshots>
			<enabled>false</enabled>
			</snapshots>
			</pluginRepository>
		</pluginRepositories>
	</profile>


 <activeProfiles>
    <activeProfile>bintray</activeProfile>
  </activeProfiles>

 創建User對象

public class User {


   private Long id;

   private String name;

   private Integer age;

   public Long getId() {
      return id;
   }

   public void setId(Long id) {
      this.id = id;
   }

   public String getName() {
      return name;
   }

   public void setName(String name) {
      this.name = name;
   }

   public Integer getAge() {
      return age;
   }

   public void setAge(Integer age) {
      this.age = age;
   }
}
 
編寫Schema
 
resources目錄下創建user.graphqls文件
schema {
    query: UserQuery
}
type UserQuery {
    user(id:Long) : User
}
type User {
    id:Long!
    name:String
    age:Int
}

 

構建schema

package cn.xiechuang.graphql.pojo;

import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.schema.GraphQLSchema;
import graphql.schema.idl.RuntimeWiring;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
import org.apache.commons.io.IOUtils;

import java.io.IOException;

public class GraphQLDemo {

    /***
     * 定義Schema*
     * <p>* schema { #定義查詢* query: UserQuery* }** @return*/
    public static GraphQLSchema createGraphqlSchema(TypeDefinitionRegistry
                                                            typeRegistry, RuntimeWiring wiring) {
        SchemaGenerator schemaGenerator = new SchemaGenerator();
        return schemaGenerator.makeExecutableSchema(typeRegistry, wiring);
    }


    /***
     * 讀取文件內容
     *
     * @param fileName classpath:文件名稱
     */
    public static String readFile(String fileName) throws IOException {
        return IOUtils.toString(GraphQLDemo.class.getClassLoader().getResourceAsStream(fileName), "utf-8");
    }


    /***
     * 定義類型的註冊器
     *
     * ** @param fileContent* @return
     * */
    public static TypeDefinitionRegistry createTypeDefinitionRegistry(String fileContent){
        SchemaParser schemaParser = new SchemaParser();
        return schemaParser.parse(fileContent);
    }


    public static RuntimeWiring createRuntimeWiring() {
        return RuntimeWiring.newRuntimeWiring()
                .type("UserQuery", typeWiring -> typeWiring
                        .dataFetcher("user", environment -> {
                            Long id = environment.getArgument("id");
                            return new User(id,"wiring"+id,15);
                        })
                ).build();
    }
    public static void main(String[] args) throws IOException {

//        讀取Schema文件
        String fileName = "user.graphqls";
        String content = readFile(fileName);

//        創建註冊器
        TypeDefinitionRegistry typeDefinitionRegistry = createTypeDefinitionRegistry(content);


//        創建resolver
        RuntimeWiring runtimeWiring = createRuntimeWiring();

//        載入Schema
        GraphQL graphQL = GraphQL.newGraphQL(createGraphqlSchema(typeDefinitionRegistry, runtimeWiring)).build();





//      使用query查詢
        ExecutionResult execute = graphQL.execute("{user(id:1){id,name}}");

        System.out.println((Object) execute.getData());

    }

}

 

測試

 

 

建議:

api可能有點複雜,過一下流程就行,代碼用的時候可以copy,下一篇章會講如何整合springboot怎麼引入到項目中,~^v^

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章