一,什么是GraphQL?
GraphQL 是由 Facebook 创造的用于描述复杂数据模型的一种查询语言。这里查询语言所指的并不是常规意义上的类似 sql 语句的查询语言,而是一种用于前后端数据查询方式的规范。
官网(中文):https://graphql.cn/
规范地址:http://spec.graphql.cn/
二、分析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/1001
#响应:
{ id : 1001, name : "张三", age : 20, address : "北京市", …… }
这样似乎是没有问题的,如果,对于这次请求,我只需要id和name属性,其他的属性我都不需要,如果我依然拿到的是全部的属性,这是不是一种资源浪费?
还有这样的一种场景,就是一次请求不能满足需求,需要有多次请求才能完成,像这样:
#查询用户信息
GET http://127.0.0.1/user/1001
#响应:
{ id : 1001, name : "张三", age : 20, address : "北京市", …… }
#查询用户的身份证信息
GET http://127.0.0.1/card/8888
#响应:
{ id : 8888, name : "张三", cardNumber : "999999999999999", address : "北京市", …… }
查询用户以及他的身份证信息,需要进行2次查询才能够完成,这样对于前端等接口的使用方是很不友好的,试想一下,如果查询信息有10个,是不是要发起10次请求才能完成?
三、进一步了解GraphQL
GraphQL很好的解决了RESTful在使用过程中的不足,接下来,我们进一步了解下它。
1、按需索取数据,避免浪费
演示地址:http://graphql.cn/learn/schema/#type-system
可以看出,当请求中只有name属性时,响应结果中只包含name属性,如果请求中添加appearsIn属性,那么结果
中就会返回appearsIn的值。
2、一次查询多个数据
可以看到,一次请求,不仅查询到了hero数据,而且还查询到了friends数据。节省了网络请求次数。
3、API的演进无需划分版本
当API进行升级时,客户端可以不进行升级,可以等到后期一起升级,这样就大大减少了客户端和服务端的耦合度。
四、GraphQL查询的规范
GraphQL定义了一套规范,用来描述语法定义,具体参考:http://graphql.cn/learn/queries/
1、字段(Fields)
在GraphQL的查询中,请求结构中包含了所预期结果的结构,这个就是字段。并且响应的结构和请求结构基本一致,这是GraphQL的一个特性,这样就可以让请求发起者很清楚的知道自己想要什么。
2、参数(Arguments)
在查询数据时,离不开传递参数,在GraphQL的查询中,也是可以传递参数的,语法:(参数名:参数值)
3、别名(Aliases)
如果一次查询多个相同对象,但是值不同,这个时候就需要起别名了,否则json的语法就不能通过了。
比如这样:
4、片段(Fragments)
查询对的属相如果相同,可以采用片段的方式进行简化定义。
五、GraphQL的Schema 和类型规范
Schema 是用于定义数据结构的,比如说,User对象中有哪些属性,对象与对象之间是什么关系等。
参考官网文档:http://graphql.cn/learn/schema/
1、Schema定义结构
schema { #定义查询
query: UserQuery
}
type UserQuery { #定义查询的类型
user(id:ID) : User #指定对象以及参数类型 }
type User { #定义对象 i
d:ID! # !表示该属性是非空项
name:String
age:Int
}
2、标量类型(Scalar Types)
GraphQL规范中,默认定义了5种类型:
- Int :有符号 32 位整数。
- Float :有符号双精度浮点值。
- String :UTF‐8 字符序列。
- Boolean : true 或者 false。
- ID :ID 标量类型表示一个唯一标识符,通常用以重新获取对象或者作为缓存中的键。
规范中定义的这5种类型,显然是不能满足需求的,所以在各种语言实现中,都有对类型进行了扩充,也就是GraphQL支持自定义类型,比如在graphql-java实现中增加了:Long、Byte等。
3、枚举类型
枚举类型是一种特殊的标量,它限制在一个特殊的可选值集合内。
enum Episode { #定义枚举
NEWHOPE
EMPIRE
JEDI
}
type Human {
id: ID!
name: String!
appearsIn: [Episode]! #使用枚举类型
homePlanet: String
}
4、接口(interface)
跟许多类型系统一样,GraphQL 支持接口。一个接口是一个抽象类型,它包含某些字段,而对象类型必须包含这些字段,才能算实现了这个接口。
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
}
六、GraphQL的Java实现
官方只是定义了规范并没有做实现,就需要有第三方来进行实现了,关
于GraphQL的java实现有几种,我们选择使用官方推荐的实现:graphql-java,我们通过该实现就可以编写GraphQL的服务端了。
官网:https://www.graphql-java.com/
github:https://github.com/graphql-java/graphql-java
1、开始使用
1.1、创建工程导入依赖
导入依赖:
<dependency> <groupId>com.graphql-java</groupId> <artifactId>graphql-java</artifactId> <version>11.0</version> </dependency>
1.2、创建User对象
@Data
public class User {
private Long id;
private String name;
private Integer age;
}
1.3、编写查询User对象实现
#对应的User定义如下
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
}
Java实现:
import static graphql.Scalars.*;
import static graphql.schema.GraphQLArgument.newArgument;
import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition;
import static graphql.schema.GraphQLObjectType.newObject;
public class GraphQLDemo {
public static void main(String[] args) {
/**
* type User { #定义对象}
*/
GraphQLObjectType userObjectType = 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)
.dataFetcher(new StaticDataFetcher(new User(1L, "张三", 20)))
.build();
/**
* type UserQuery { #定义查询的类型}
*/
GraphQLObjectType userQueryObjectType = newObject()
.name("UserQuery")
.field(userFieldDefinition)
.build();
/**
* schema { #定义查询 }
*/
GraphQLSchema graphQLSchema = GraphQLSchema.newSchema().query(userQueryObjectType).build();
GraphQL graphQL = GraphQL.newGraphQL(graphQLSchema).build();
String query = "{user{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());
}
}
运行结果:
{User={id=1, name=张三, age=20}}
1.4、设置查询参数
修改GraphQLFieldDefinition对象:
GraphQLFieldDefinition userFieldDefinition = newFieldDefinition()
.name("user")
.type(userObjectType)
.argument(newArgument().name("id").type(GraphQLLong).build())
.dataFetcher(environment -> {//查询数据库的方法
Long id = environment.getArgument("id");
// 查询数据库了
// TODO 先模式实现
return new User(id, "张三:"+id, 20+id.intValue(), null);
})
// .dataFetcher(new StaticDataFetcher(new User(1L, "张三", 20)))
.build();
String query = "{user(id:100){id,name,age}}";
测试:
query:{user(id:100){id,name,age}}
{data={user={id=100, name=张三:100, age=120}}}
2、使用SDL构建schema
graphql-java 提供了两种不同的方式来定义模式:以编程方式作为Java代码或通过特殊的graphql dsl(称为SDL)
2.1、创建user.graphqls文件
在resources目录下创建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
}
2、构建schema与对象的嵌套
创建Card对象:
@Data
public class Card {
private String cardNumber;
private Long userId;
}
修改User对象:
@Data
public class User {
private Long id;
private String name;
private Integer age;
private Card card;
}
构建schema:
public class GraphQLSDLDemo {
public static void main(String[] args) throws IOException {
// 读取GraphQL文件,进行解析
String fileName = "user.graphqls";
String fileContent = IOUtils.toString(GraphQLSDLDemo.class.getClassLoader().getResource(fileName), "UTF-8");
TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(fileContent);
// 解决的是数据的查询
RuntimeWiring wiring = RuntimeWiring.newRuntimeWiring()
.type("UserQuery", builder ->
builder.dataFetcher("user", environment -> {
Long id = environment.getArgument("id");
Card card = new Card("123456789", id);
return new User(id, "张三:" + 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:100){id,name,age,card{cardNumber}}}";
ExecutionResult result = graphQL.execute(query);
System.out.println("query:" + query);
System.out.println(result.toSpecification());
}
}
测试结果:
query:{user(id:100){id,name,age,card{cardNumber}}}
{data={user={id=100, name=张三:100, age=120, card={cardNumber=123456789}}}}
七、chrome安装GraphQL client插件
可以在https://www.extfans.com/下载插件,安装到谷歌浏览器。
使用测试:
八、GraphQL客户端
用Apollo Client作为前端使用的GraphQL客户端使用。
参考文档:https://www.apollographql.com/docs/react/essentials/get-started.html
1、安装依赖
npm install apollo-boost react-apollo graphql --save
2、创建客户端
import ApolloClient from "apollo-boost";
const client = new ApolloClient({ uri: "http://127.0.0.1:18080/graphql" });
3、创建查询
import gql from "graphql-tag";
//定义查询
const GET_INDEX_ADS = gql` { IndexAdList{ list{original } } } `;
let swipe = new Promise((resolve, reject) => { client.query({query: GET_INDEX_ADS}).then(result => resolve(result.data.IndexAdList.list)); })
4.后端Controller
@RequestMapping("graphql")
@Controller
@CrossOrigin
public class GraphQLController {
@Autowired
private GraphQL graphQL;
private static final ObjectMapper MAPPER = new ObjectMapper();//解析json
/**
* 实现GraphQL查询
*
* @param query
* @return
*/
@GetMapping
@ResponseBody
public Map<String, Object> query(@RequestParam("query") String query) {
return this.graphQL.execute(query).toSpecification();
}
@PostMapping
@ResponseBody
public Map<String, Object> postQuery(@RequestBody String json) {
try {
JsonNode jsonNode = MAPPER.readTree(json);
if(jsonNode.has("query")){
String query = jsonNode.get("query").textValue();
return this.graphQL.execute(query).toSpecification();
}
} catch (IOException e) {
e.printStackTrace();
}
Map<String, Object> error = new HashMap<>();
error.put("status", 500);
error.put("msg", "查询出错");
return error;
}
}
测试即可。