1、什么是GraphQL?
GraphQL 是由 Facebook 创造的用于描述复杂数据模型的一种查询语言。这里查询语言所指的并不是常规意义上的类似 sql 语句的查询语言,而是一种用于前后端数据查询方式的规范。
官网(中文):添加链接描述
规范地址:添加链接描述
传统方式操作资源
http://127.0.0.1/item/queryUser?id=1 查询,GET
http://127.0.0.1/item/saveUser 新增,POST
http://127.0.0.1/item/updateUser 更新,POST
http://127.0.0.1/item/deleteUser?id=1 删除,GET或POST
restful方式操作资源
http://127.0.0.1/item/User?id=1 查询,GET
http://127.0.0.1/item/User 新增,POST
http://127.0.0.1/item/User 更新,POST
http://127.0.0.1/item/User?id=1 删除,GET或POST
restful不太友好的地方:在查询的时候、是这样的
http://127.0.0.1/item/User?id=1 查询,GET
#返回数据
{
id:123,
name:"张三",
age:20,
like:"抽烟喝酒打麻将"
这次查询我只想要名字、id、age、like就是多余的、我都拿到的话是一种资源的浪费。
还有一种情况、一次请求不能满足需求,需要有多次请求才能完成,像这样:
http://127.0.0.1/item/User?id=1 查询,GET
#查询用户、返回数据
{
id:123,
name:"张三",
age:20,
like:"抽烟喝酒打麻将"
}
#查询用户住址
http://127.0.0.1/item/address?id=123 查询,GET
{
id:123,
name:"张三",
age:20,
like:"抽烟喝酒打麻将",
address:"上海",
....
}
查询用户以及他的住址信息,需要进行2次查询才能够完成,这样对于前端等接口的使用方是很不友好。
2、GraphQL优点
2.1GraphQL请求可以获得你想要的数据
2.2一次请求、不仅查询到hero数据、还查询到friends数据、节省了网络请求次数。获取多个资源只用一次请求
2.3API 演进无需划分版本给你的 GraphQL API 添加字段和类型而无需影响现有查询。老旧的字段可以废弃,从工具中隐藏。通过使用单一演进版本,GraphQL API 使得应用始终能够使用新的特性,并鼓励使用更加简洁、更好维护的服务端代码。
GraphQL很好的解决了RESTful在使用过程中的不足。
3、GraphQL规范
GraphQL定义了一套规范,用来描述语法定义,具体参考:http://graphql.cn/learn/queries/(仅仅是规范、并不是具体实现、需要各种语言进行实现)
下面介绍一些基本规范
3.1字段(Fields)
3.2参数
3.3别名
3.4片段
{
leftComparison: hero(episode: EMPIRE) {
...comparisonFields
}
rightComparison: hero(episode: JEDI) {
...comparisonFields
}
}
fragment comparisonFields on Character {
name
appearsIn
friends {
name
}
}
结果
{
"data": {
"leftComparison": {
"name": "Luke Skywalker",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
],
"friends": [
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
},
{
"name": "C-3PO"
},
{
"name": "R2-D2"
}
]
},
"rightComparison": {
"name": "R2-D2",
"appearsIn": [
"NEWHOPE",
"EMPIRE",
"JEDI"
],
"friends": [
{
"name": "Luke Skywalker"
},
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
}
]
}
}
}
你可以看到上面的查询如何漂亮地重复了字段。片段的概念经常用于将复杂的应用数据需求分割成小块,特别是你要将大量不同片段的 UI 组件组合成一个初始数据获取的时候。
3.5操作名称(Operation name)(类似函数)
这之前,我们都使用了简写句法,省略了 query 关键字和查询名称,但是生产中使用这些可以使我们代码减少歧义。
下面的示例包含了作为操作类型的关键字 query 以及操作名称 HeroNameAndFriends:
操作类型可以是 query、mutation 或 subscription,描述你打算做什么类型的操作。操作类型是必需的,除非你使用查询简写语法,在这种情况下,你无法为操作提供名称或变量定义。
操作名称是你的操作的有意义和明确的名称。它仅在有多个操作的文档中是必需的,但我们鼓励使用它,因为它对于调试和服务器端日志记录非常有用。 当在你的网络日志或是 GraphQL 服务器中出现问题时,通过名称来从你的代码库中找到一个查询比尝试去破译内容更加容易。 就把它想成你喜欢的程序语言中的函数名。例如,在 JavaScript 中,我们只用匿名函数就可以工作,但是当我们给了函数名之后,就更加容易追踪、调试我们的代码,并在其被调用的时候做日志。同理,GraphQL 的查询和变更名称,以及片段名称,都可以成为服务端侧用来识别不同 GraphQL 请求的有效调试工具。
3.6变量(Variables)
目前为止,我们将参数写在了查询字符串内。但是在很多应用中,字段的参数可能是动态的:例如,可能是一个"下拉菜单"让你选择感兴趣的《星球大战》续集,或者是一个搜索区,或者是一组过滤器。
将这些动态参数直接传进查询字符串并不是好主意,因为这样我们的客户端就得动态地在运行时操作这些查询字符串了,再把它序列化成 GraphQL 专用的格式。其实,GraphQL 拥有一级方法将动态值提取到查询之外,然后作为分离的字典传进去。这些动态值即称为变量。
使用变量之前,我们得做三件事:
使用 $variableName 替代查询中的静态值。
声明 $variableName 为查询接受的变量之一。
将 variableName: value 通过传输专用(通常是 JSON)的分离的变量字典中。
全部做完之后就像这个样子:
变量定义(Variable definitions)
变量定义看上去像是上述查询中的 ($episode: Episode)。其工作方式跟类型语言中函数的参数定义一样。它以列出所有变量,变量前缀必须为 $,后跟其类型,本例中为 Episode。
所有声明的变量都必须是标量、枚举型或者输入对象类型。所以如果想要传递一个复杂对象到一个字段上,你必须知道服务器上其匹配的类型。可以从Schema页面了解更多关于输入对象类型的信息。
变量定义可以是可选的或者必要的。上例中,Episode 后并没有 !,因此其是可选的。但是如果你传递变量的字段要求非空参数,那变量一定是必要的。
如果想要进一步了解变量定义的句法,可以学习 GraphQL 的 schema 语言。schema 语言在 Schema 中有细述。
默认变量(Default variables)
可以通过在查询中的类型定义后面附带默认值的方式,将默认值赋给变量。
3.7指令(Directives)
我们上面讨论的变量使得我们可以避免手动字符串插值构建动态查询。传递变量给参数解决了一大堆这样的问题,但是我们可能也需要一个方式使用变量动态地改变我们查询的结构。譬如我们假设有个 UI 组件,其有概括视图和详情视图,后者比前者拥有更多的字段。
我们来构建一个这种组件的查询:
尝试修改上面的变量,传递 true 给 withFriends,看看结果的变化。
我们用了 GraphQL 中一种称作指令的新特性。一个指令可以附着在字段或者片段包含的字段上,然后以任何服务端期待的方式来改变查询的执行。GraphQL 的核心规范包含两个指令,其必须被任何规范兼容的 GraphQL 服务器实现所支持:
@include(if: Boolean) 仅在参数为 true 时,包含此字段。
@skip(if: Boolean) 如果参数为 true,跳过此字段。
指令在你不得不通过字符串操作来增减查询的字段时解救你。服务端实现也可以定义新的指令来添加新的特性。
…
4、Schema 和类型
Schema 是用于定义数据结构的,比如说,User对象中有哪些属性,对象与对象之间是什么关系等。
参考官网文档:http://graphql.cn/learn/schema/
4.1Schema定义结构
schema { #定义查询
query: UserQuery
}
type UserQuery { #定义查询的类型
user(id:ID) : User #指定对象以及参数类型
}
type User { #定义对象
id:ID! # !表示该属性是非空项
name:String
age:Int
}
4.2GraphQL 自带一组默认标量类型:
Int:有符号 32 位整数。
Float:有符号双精度浮点值。
String:UTF‐8 字符序列。
Boolean:true 或者 false。
ID:ID 标量类型表示一个唯一标识符,通常用以重新获取对象或者作为缓存中的键。ID 类型使用和 String 一样的方式序列化;然而将其定义为 ID 意味着并不需要人类可读型。
规范中定义的五种类型、无法满足需求、所以在各种语言实现中、都有对类型进行了扩充。也就是GraphQL支持自定义类型。比如在graphql-java实现中增加了:Long、Byte等。
4.3枚举类型
枚举类型是一种特殊的标量,它限制在一个特殊的可选值集合内
enum Episode { #定义枚举
NEWHOPE
EMPIRE
JEDI
}
type Human {
id: ID!
name: String!
appearsIn: [Episode]! #使用枚举类型
homePlanet: String
}
4.4接口(interface)
跟许多类型系统一样,GraphQL 支持接口。一个接口是一个抽象类型,它包含某些字段,而对象类型必须包含这
些字段,才能算实现了这个接口。
例如,你可以用一个 Character 接口用以表示《星球大战》三部曲中的任何角色:
//!表示非空
/[Episode!]! 表示一个 Episode 数组。因为它也是非空的,所以当你查询 appearsIn 字段的时候,你也总能得到一个数组(零个或者多个元素)。且由于 Episode! 也是非空的,你总是可以预期到数组中的每个项目都是一个 Episode 对象。
interface Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
}
这意味着任何实现 Character 的类型都要具有这些字段,并有对应参数和返回类型。
例如,这里有一些可能实现了 Character 的类型:
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
}
5、GraphQLjava的实现
我们选择使用官方推荐的实现:graphql-java,我们通过该实现就可以编写
GraphQL的服务端了。
官网:https://www.graphql-java.com/
github:https://github.com/graphql-java/graphql-java
5.1创建graphql-java工程
5.2导入坐标
<dependencies>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java</artifactId>
<version>14.0</version>
</dependency>
</dependencies>
5.3创建User对象
package com.graphql.vo;
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;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
5.4编写查询User对象实现demo类
package com.graphql.demo;
import com.graphql.vo.User;
import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.Scalars;
import graphql.schema.*;
import javax.jws.soap.SOAPBinding;
import static graphql.Scalars.*;
import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition;
public class demo {
public static void main(String[] args) {
//type User定义对象
GraphQLObjectType userObjectType = GraphQLObjectType.newObject()
.name("User")
.field(newFieldDefinition().name("id").type(GraphQLLong))
.field(newFieldDefinition().name("name").type(GraphQLString))
.field(newFieldDefinition().name("age").type(GraphQLInt))
.build();
//user:user #指定对象以及参数类型
GraphQLFieldDefinition userFieldDefinition = newFieldDefinition()
.name("user")
.type(userObjectType)
.argument(GraphQLArgument.newArgument().name("id").type(GraphQLLong).build())
.dataFetcher(environments -> {
Long id = environments.getArgument("id");
System.out.println(id);
System.out.println("-------------------------");
//查询数据库
//TODO 先模拟实现
return new User(1L,"张三丰:"+id,20 + id.intValue());
})
// .dataFetcher(new StaticDataFetcher(new User(1L,"张三",20)))
.build();
//type UserQuery 定义查询的类型
GraphQLObjectType userQueryObjectType = GraphQLObjectType.newObject()
.name("UserQuery")
.field(userFieldDefinition)
.build();
//schema 定义查询
GraphQLSchema graphQLSchema = GraphQLSchema.newSchema().query(userQueryObjectType).build();
GraphQL graphQL = GraphQL.newGraphQL(graphQLSchema).build();
String query = "{user(id:1){id,name,age}}";
ExecutionResult result = graphQL.execute(query);
System.out.println("query" + query);
// System.out.println(result.getErrors());
// System.out.println(result.getData());
System.out.println(result.toSpecification());
}
}
6、GraphQLjava配置文件实现
6.1创建user.graphqls文件(idea可以下载jsGraphQL插件显示文件)
schema {
query: UserQuery
}
type UserQuery {
user(id:Long) : User
}
type User {
id:Long!
name:String
age:Int
}
6.2创建GraphQLSDLDemo类
package com.graphql.demo;
import com.graphql.vo.User;
import com.sun.corba.se.impl.orbutil.graph.Graph;
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 GraphQLSDLDemo {
public static void main(String[] args) throws Exception {
//读取GraphQL文件、进行解析
String fileName = "user.graphqls";
//获取文件内容
String fileContent = IOUtils.toString(GraphQLSDLDemo.class.getClassLoader().getResource(fileName),"UTF-8");
//通过SchemaParser解析器解析文件得到TypeDefinitionRegistry注册器
TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(fileContent);
//解决数据的查询
RuntimeWiring wiring = RuntimeWiring.newRuntimeWiring()
.type("UserQuery",typeWiring -> typeWiring
.dataFetcher("user",environment -> {
Long id = environment.getArgument("id");
return new User(1L,"张三丰:"+id,20 + id.intValue());
})
)
.build();
//生成Schema
GraphQLSchema graphQLSchema = new SchemaGenerator().makeExecutableSchema(typeRegistry,wiring);
//根据Schema对象生成GraphQL对象
GraphQL graphQL = GraphQL.newGraphQL(graphQLSchema).build();
String query = "{user(id:1){id,name,age}}";
ExecutionResult result = graphQL.execute(query);
System.out.println("query" + query);
System.out.println(result.toSpecification());
}
}
7、GraphQLjava配置文件查询子类
7.1创建card类
package com.graphql.vo;
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;
}
}
7.2在User类中添加card字段、并添加set和get、重新生成构造函数
7.3修改GraphQLSDLDemo并运行测试
package com.graphql.demo;
import com.graphql.vo.Card;
import com.graphql.vo.User;
import com.sun.corba.se.impl.orbutil.graph.Graph;
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 GraphQLSDLDemo {
public static void main(String[] args) throws Exception {
//读取GraphQL文件、进行解析
String fileName = "user.graphqls";
//获取文件内容
String fileContent = IOUtils.toString(GraphQLSDLDemo.class.getClassLoader().getResource(fileName),"UTF-8");
//通过SchemaParser解析器解析文件得到TypeDefinitionRegistry注册器
TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(fileContent);
//解决数据的查询
RuntimeWiring wiring = RuntimeWiring.newRuntimeWiring()
.type("UserQuery",typeWiring -> typeWiring
.dataFetcher("user",environment -> {
Long id = environment.getArgument("id");
Card card = new Card("123123123",id);
return new User(1L,"张三丰:"+id,20 + id.intValue(),card);
})
)
.build();
//生成Schema
GraphQLSchema graphQLSchema = new SchemaGenerator().makeExecutableSchema(typeRegistry,wiring);
//根据Schema对象生成GraphQL对象
GraphQL graphQL = GraphQL.newGraphQL(graphQLSchema).build();
String query = "{user(id:1){id,name,age,card{cardNumber,userId}}}";
ExecutionResult result = graphQL.execute(query);
System.out.println("query" + query);
System.out.println(result.toSpecification());
}
}