GraphQL简介及入门

1、什么是GraphQL?

GraphQL 是由 Facebook 创造的用于描述复杂数据模型的一种查询语言。这里查询语言所指的并不是常规意义上的类似 sql 语句的查询语言,而是一种用于前后端数据查询方式的规范。官网(中文):https://graphql.cn/;规范地址:http://spec.graphql.cn/
在这里插入图片描述

2、RESTful存在的问题

RESTful是我们已经很熟悉的用于api通信的规范,如这样:

GET http://127.0.0.1/user/1 #查询
POST http://127.0.0.1/user #新增
PUT http://127.0.0.1/user #更新
DELETE http://127.0.0.1/user #删除

在查询的时候,往往是这样:

#请求
GET http://127.0.0.1/user/1
#响应:
{
 id : 1,
 name : "张三",
 age : 20,
 address : "北京市",
 ……
}

这样似乎是没有问题的,如果,对于这次请求,我只需要id和name属性,其他的属性我都不需要,如果我依然拿到的是全部的属性,这是不是一种资源浪费?还有这样的一种场景,就是一次请求不能满足需求,需要有多次请求才能完成,像这样:

#查询用户信息
GET http://127.0.0.1/user/1
#响应:
{
 id : 1,
 name : "张三",
 age : 20,
 address : "北京市",
 ……
}
#查询用户的证件信息
GET http://127.0.0.1/card/8888
#响应:
{
 id : 8888,
 name : "张三",
 cardNumber : "666666666666",
 address : "北京市",
}

查询用户以及他的信息,需要进行2次查询才能够完成,这样对于前端等接口的使用方是很不友好的,试想一下,如果查询信息有10个,是不是要发起10次请求才能完成?

3、了解GraphQL优势

GraphQL很好的解决了RESTful在使用过程中的不足,接下来,我们进一步了解下它。

3.1、按需索取数据,避免浪费

示例地址:http://graphql.cn/learn/schema/#type-system
在这里插入图片描述
在这里插入图片描述
可以看出,当请求中只有name属性时,响应结果中只包含name属性,如果请求中添加appearsIn属性,那么结果中就会返回appearsIn的值。

3.2、一次查询多个数据

在这里插入图片描述
可以看到,一次请求,不仅查询到了hero数据,而且还查询到了friends数据。节省了网络请求次数。

3.3、API的演进无需划分版本

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4、GraphQL查询的规范

GraphQL定义了一套规范,用来描述语法定义,具体参考:http://graphql.cn/learn/queries/

4.1、字段(Fields)

在GraphQL的查询中,请求结构中包含了所预期结果的结构,这个就是字段。并且响应的结构和请求结构基本一致,这是GraphQL的一个特性,这样就可以让请求发起者很清楚的知道自己想要什么。
在这里插入图片描述

4.2、参数(Arguments)

在查询数据时,离不开传递参数,在GraphQL的查询中,也是可以传递参数的,语法:(参数名:参数值)
在这里插入图片描述

4.3、别名(Aliases)

如果一次查询多个相同对象,但是值不同,这个时候就需要起别名了,否则json的语法就不能通过了。比如这样:
在这里插入图片描述

4.4、片段(Fragments)

查询对的属相如果相同,可以采用片段的方式进行简化定义。
在这里插入图片描述

5、GraphQL的Schema 和类型规范

Schema 是用于定义数据结构的,比如说,User对象中有哪些属性,对象与对象之间是什么关系等。参考官网文档:http://graphql.cn/learn/schema/

5.1、Schema定义结构
schema {  #定义查询
 query: UserQuery
}
type UserQuery { #定义查询的类型
 user(id:ID) : User #指定对象以及参数类型
}
type User { #定义对象
 id:ID! # !表示该属性是非空项
 name:String
 age:Int
}
5.2、标量类型(Scalar Types)

GraphQL规范中,默认定义了5种类型:
Int :有符号 32 位整数。
Float :有符号双精度浮点值。
String :UTF‐8 字符序列。
Boolean : true 或者 false 。
ID :ID 标量类型表示一个唯一标识符,通常用以重新获取对象或者作为缓存中的键。
规范中定义的这5种类型,显然是不能满足需求的,所以在各种语言实现中,都有对类型进行了扩充,也就是GraphQL支持自定义类型,比如在graphql-java实现中增加了:Long、Byte等。

5.3、枚举类型

枚举类型是一种特殊的标量,它限制在一个特殊的可选值集合内。

enum Episode { #定义枚举
NEWHOPE
EMPIRE
JEDI
}
type Human {
 id: ID!
 name: String!
 appearsIn: [Episode]! #使用枚举类型
 homePlanet: String
}
5.4、接口(interface)

跟许多类型系统一样,GraphQL 支持接口。一个接口是一个抽象类型,它包含某些字段,而对象类型必须包含这些字段,才能算实现了这个接口。例如,你可以用一个 Character 接口用以表示《星球大战》三部曲中的任何角色:

interface Character {  #定义接口
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
}
#实现接口
type Human implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
starships: [Starship]
totalCredits: Int
}
type Droid implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
primaryFunction: String
}

6、GraphQL的Java实现

通过前面的讲解,我们对GraphQL有了解,官方只是定义了规范并没有做实现,就需要有第三方来进行实现了,关于GraphQL的java实现有几种,我们选择使用官方推荐的实现:graphql-java,我们通过该实现就可以编写GraphQL的服务端了。
官网:https://www.graphql-java.com/
github:https://github.com/graphql-java/graphql-java
在这里插入图片描述

6.1、入门示例

导入依赖:
graphql-java包并没有发布到maven中央仓库,需要配置第三方仓库才能使用。

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

在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>

创建User对象

public class User {
private Long id;
  private String name;
  private Integer age;
  public User() {
 }
  public User(Long id, String name, Integer age) {
    this.id = id;
    this.name = name;
    this.age = 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;
 }
}

编写查询User对象实现

#对应的User定义如下
schema {  #定义查询
 query: UserQuery
}
type UserQuery { #定义查询的类型
 user : User #指定对象以及参数类型
}
type User { #定义对象
 id:Long! # !表示该属性是非空项
 name:String
  age:Int
}

Java实现:

import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.schema.GraphQLFieldDefinition;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLSchema;
import graphql.schema.StaticDataFetcher;
import static graphql.Scalars.*;
import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition;
import static graphql.schema.GraphQLObjectType.newObject;
public class GraphQLDemo {
  /**
  * 定义Schema
  * <p>
  * schema { #定义查询
  *   query: UserQuery
  * }
  *
  * @return
  */
  public static GraphQLSchema createGraphqlSchema(GraphQLFieldDefinition
userDefinition) {
    GraphQLObjectType userQuery = newObject().name("userQuery")
       .field(userDefinition).build();
    return GraphQLSchema.newSchema().query(userQuery).build();
 }
  /**
  * 定义查询的类型
  * <p>
  * type UserQuery { #定义查询的类型
  *   user : User #指定对象
  * }
  *
  * @return
  */
  public static GraphQLFieldDefinition createUserDefinition(GraphQLObjectType
userType) {
    return newFieldDefinition()
       .name("User")
       .type(userType)
        //静态数据
       .dataFetcher(new StaticDataFetcher(new User(1L, "张三", 20)))
       .build();
 }
  /**
  * 定义User对象类型
  *
  * type User { #定义对象
  *   id:Long! # !表示该属性是非空项
  *   name:String
  *   age:Int
  * }
  *
  *
  * @return
  */
  public static GraphQLObjectType createUserObjectType(){
    return newObject()
       .name("User")
       .field(newFieldDefinition().name("id").type(GraphQLLong))
       .field(newFieldDefinition().name("name").type(GraphQLString))
       .field(newFieldDefinition().name("age").type(GraphQLInt))
       .build();
 }
  public static void main(String[] args) {
    GraphQLObjectType userObjectType = createUserObjectType();
    GraphQLFieldDefinition userDefinition =
createUserDefinition(userObjectType);
    GraphQL graphQL =
GraphQL.newGraphQL(createGraphqlSchema(userDefinition)).build();
    String query = "{User{id,name}}";
    ExecutionResult executionResult = graphQL.execute(query);
    // 打印错误
    System.out.println("错误:" + executionResult.getErrors());
    // 打印数据
    System.out.println("结果:" +(Object) executionResult.getData());
 }
}

运行结果:

查询字符串:{User{id,name,age}}
错误:[]
结果:{User={id=1, name=张三, age=20}}
---------------
查询字符串:{User{id,name}}
错误:[]
结果:{User={id=1, name=张三}}

设置查询参数 修改createUserDefinition()方法:

public static GraphQLFieldDefinition createUserDefinition(GraphQLObjectType
userType) {
    return newFieldDefinition()
       .name("User")
       .type(userType)
        //静态数据
     //  .dataFetcher(new StaticDataFetcher(new User(1L, "张三", 20)))
        // 设置参数
       .argument(newArgument().name("id").type(GraphQLLong).build())
       .dataFetcher(environment -> {
          Long id = environment.getArgument("id");
          return new User(id, "张三_"+id, 20 + id.intValue());
       })
       .build();
 }

结果:

查询字符串:{User(id:1){id,name}}
错误:[]
结果:{User={id=1, name=张三_1}}
----------------
查询字符串:{User(id:1){id,name,age}}
错误:[]
结果:{User={id=1, name=张三_1, age=21}}
6.2、使用SDL构建schema

graphql-java 提供了两种不同的方式来定义模式:以编程方式作为Java代码或通过特殊的graphql dsl(称为SDL)。

创建user.graphqls文件
在resources目录下创建user.graphqls文件:

schema {
 query: UserQuery
}
type UserQuery {
 user(id:Long) : User
}
type User {
 id:Long!
 name:String
 age:Int
}

安装GraphQL插件:

在这里插入图片描述

构建schema

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 javax.smartcardio.Card;
import java.io.IOException;
import java.util.List;
public class GraphQLSDLDemo {
/**
  * 定义Schema
  * <p>
  * schema { #定义查询
  *   query: UserQuery
  * }
  *
  * @return
  */
  public static GraphQLSchema createGraphqlSchema(TypeDefinitionRegistry
typeRegistry, RuntimeWiring wiring) {
    SchemaGenerator schemaGenerator = new SchemaGenerator();
    return schemaGenerator.makeExecutableSchema(typeRegistry, wiring);
 }
  /**
  * 定义类型的注册器
  *
  * @param fileContent
  * @return
  */
  public static TypeDefinitionRegistry createTypeDefinitionRegistry(String
fileContent){
    SchemaParser schemaParser = new SchemaParser();
    return schemaParser.parse(fileContent);
 }
  /**
  * 读取文件内容
  *
  * @param fileName
  * @return
  */
  public static String readFileToString(String fileName){
    try {
      return
IOUtils.toString(GraphQLSDLDemo.class.getClassLoader().getResourceAsStream(fileName
), "UTF-8");
} catch (IOException e) {
      e.printStackTrace();
   }
    return null;
 }
  public static RuntimeWiring createRuntimeWiring() {
    return RuntimeWiring.newRuntimeWiring()
       .type("UserQuery", typeWiring -> typeWiring
           .dataFetcher("user", environment -> {
              Long id = environment.getArgument("id");
              return new User(id, "张三_"+id, 20 + id.intValue());
           })
       ).build();
 }
  public static void main(String[] args) {
    String fileName = "user.graphqls";
    TypeDefinitionRegistry registry =
createTypeDefinitionRegistry(readFileToString(fileName));
    RuntimeWiring runtimeWiring = createRuntimeWiring();
    GraphQL graphQL = GraphQL.newGraphQL(createGraphqlSchema(registry,
runtimeWiring)).build();
    // 注意:查询语句中的user是小写,要和user.graphqls文件中的属性名一致
    String query = "{user(id:1){id,name,age}}";
    ExecutionResult executionResult = graphQL.execute(query);
    System.out.println("查询字符串:" + query);
    // 打印错误
    System.out.println("错误:" + executionResult.getErrors());
    // 打印数据
    System.out.println("结果:" +(Object) executionResult.getData());
 }
}

运行结果:

查询字符串:{user(id:1){id,name,age}}
错误:[]
结果:{user={id=1, name=张三_1, age=21}}
---------------------
查询字符串:{user(id:1){id,name}}
错误:[]
结果:{user={id=1, name=张三_1}}

对象嵌套 我们创建一个Card对象,User和Card对象为一对一的关系。

public class Card {
  private String cardNumber;
  private Long userId;
  public Card() {
 }
  public Card(String cardNumber, Long userId) {
    this.cardNumber = cardNumber;
    this.userId = userId;
 }
  public String getCardNumber() {
    return cardNumber;
 }
  public void setCardNumber(String cardNumber) {
    this.cardNumber = cardNumber;
 }
  public Long getUserId() {
    return userId;
 }
  public void setUserId(Long userId) {
    this.userId = userId;
 }
}
public class User {
  private Long id;
  private String name;
  private Integer age;
  private Card card;
  public User() {
 }
  public User(Long id, String name, Integer age) {
    this.id = id;
    this.name = name;
    this.age = age;
 }
  public User(Long id, String name, Integer age, Card card) {
    this.id = id;
    this.name = name;
    this.age = age;
    this.card = card;
 }
  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;
 }
  public Card getCard() {
    return card;
 }
  public void setCard(Card card) {
    this.card = card;
 }
}

修改user.graphqls文件

schema {
 query: UserQuery
}
type UserQuery {
 user(id:Long) : User
}
type User {
 id:Long!
 name:String
 age:Int
 card:Card
}
type Card {
 cardNumber:String!
 userId:Long
}

修改GraphQLSDLDemo逻辑:GraphQL并不实现关联查询,需要自己实现。

public static RuntimeWiring createRuntimeWiring() {
    return RuntimeWiring.newRuntimeWiring()
       .type("UserQuery", typeWiring -> typeWiring
           .dataFetcher("user", environment -> {
              Long id = environment.getArgument("id");
              return new User(id, "张三_"+id, 20 + id.intValue(),new
Card("number_"+id, id));
           })
       ).build();
 }
查询字符串:{user(id:1){id,name}}
错误:[]
结果:{user={id=1, name=张三_1}}
-----------------
查询字符串:{user(id:1){id,name,card{cardNumber}}}
错误:[]
结果:{user={id=1, name=张三_1, card={cardNumber=number_1}}}
-----------------
查询字符串:{user(id:1){id,name,card{cardNumber,userId}}}
错误:[]
结果:{user={id=1, name=张三_1, card={cardNumber=number_1, userId=1}}}

最后说下个人使用观点:
使用起来确实很繁琐,需要定义规则,包装请求接口等;
而且为了兼容所有使用到的接口,可能需要每次都从数据库查最多的字段(比如一个接口要id,name;另外的接口则需要使用id,name,phone,email,…);
还有前端每次查询也需要指定自己需要的字段。

发布了105 篇原创文章 · 获赞 7 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_43792385/article/details/100263751