GraphQL介绍与SpringBoot整合GraphQL
程序员文章站
2022-05-17 08:48:07
...
一.查询规范
- 参考官网文档:https://graphql.cn/learn/queries/#variables
1.字段(Fields): 在GraphQL的查询中,请求结构中包含了所预期结果的结构,这个就是字段。并且响应的结构和请求结构基本一致
- 语法
{ 查询对象{ 查询字段1 查询字段2 } }
- 例子:查询所有hero对象的name字段
2.参数(Arguments):在查询数据时,离不开传递参数,在GraphQL的查询中也是可以传递参数
- 语法
{ 查询对象(参数名:"参数值"){ 查询字段1 查询字段2 } }
- 例子:查询所有id=1000的hero对象 并只返回name字段
3.别名(Aliases):如果一次查询多个相同对象,但是值不同,这个时候就需要起别名了,否则json的语法就不能通过了。
- 语法
{ 对象别名1:查询对象(参数名1:参数值1){ 查询字段1 查询字段2 } 对象别名2:查询对象(参数名2:参数值2){ 查询字段1 查询字段2 } }
- 例子:同时查询 “episode=EMPIRE” 和 “episode=JEDI” 的hero对象
4.片段(Fragments):如果查询多个对象的 但查询的属性如果相同,可以采用片段的方式进行简化定义。
- 语法
{ 对象别名1:查询对象(参数名1:参数值1){ 片段名称 } 对象别名2:查询对象(参数名2:参数值2){ 片段名称 } } fragment 片段名称 on Character{ 字段1 字段2 }
- 例子
二.GraphQL的Schema 和类型规范
- 参考官网文档:http://graphql.cn/learn/schema/
- 实际GraphQL是Restful的改进 同样是以对象作为查询条件 也将该对象作为返回数据 拓展功能有
- 在查询时可以指定用该对像的哪些字段的值查询
- 在返回对象时可以指定返回对象部分字段的值
- 可以查询根据多个不同查询条件查询多个对象(通过别名)
- 支持对象嵌套
1.Schema定义结构
#定义查询
schema {
query: 查询类型名称
}
#定义查询的类型
type 查询类型 {
#设置对象 该对象可用于查询与返回
查询对象名称(字段名:字段类型) : 对象类型
}
#定义对象类型(与java中实体类一般相同)
type 对象类型 {
字段1:类型! # !表示该属性是非空项
字段2:类型
字段3:类型
}
2.标量类型(Scalar Types)
- 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]! #使用枚举类型
}
4.接口(interface)
- 跟许多类型系统一样,GraphQL 支持接口。一个接口是一个抽象类型,它包含某些字段,而对象类型必须包含这些字段,才能算实现了这个接口。
#定义接口
interface Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
}
#必须实现Character的四个属性
type Human implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
starships: [Starship]
totalCredits: Int
}
#必须实现Character的四个属性
type Droid implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
primaryFunction: String
}
三.SpringBoot整合GraphQL(使用SDL构建schema)
1.依赖
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java</artifactId>
<version>11.0</version>
</dependency>
2.在resources下创建.graphqls文件(例子:user.graphqls)
#定义查询
schema {
query: UserQuery
}
#定义查询类型
type UserQuery {
#设置对象uesr 类型为User 即查询条件为User的字段值 返回值也为User的指定字段值
user(id:Long) : User
}
#定义查询条件中对象类型(与实体类User相同)
type User {
id:Long!
name:String
age:Int
}
3.建立一个package:graphqls ,然后在该包下新建一个GraphqlProvider
@Component
public class GraphqlProvider {
private GraphQL graphQL;
@Autowired
private List<MyDataFetcher> myDataFetchers;
//给spring注入配置好的GraphQL 外面可以调用
@Bean
private GraphQL graphQL(){
return this.graphQL;
}
//实现对GraphQL对象的初始化
@PostConstruct
public void init() throws FileNotFoundException {
//读取刚才在resources下创建的user.graphqls
//如果不采用注入spring的方式 也可以调用方传入指定file名称
File file = ResourceUtils.getFile("classpath:user.graphqls");
this.graphQL = GraphQL.newGraphQL(buildGraphQLSchema(file)).build();
}
//通过读取的.graphqls文件构建Schema
private GraphQLSchema buildGraphQLSchema(File file) {
//解析.graphqls文件
TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(file);
return new SchemaGenerator().makeExecutableSchema(typeRegistry, buildWiring());
}
//设置查找到的数据
private RuntimeWiring buildWiring() {
return RuntimeWiring.newRuntimeWiring()
//UserQuery:schema中定义的查询类型名称
.type("UserQuery", builder-> builder
//user:查询类型中对象类型的名称
.dataFetcher("user", environment -> {
//id:该对象设置的查询参数名
Long id = environment.getArgument("id");
//此处应该查询数据库(省略...)
//返回查询结果
return new User(id, "张三_"+id, 20 + id.intValue());
})
).build();
}
}
4.注入GraphQL 并使用
@Controller
@RequestMapping("graphql")
public class GraphQLController {
@Autowired
private GraphQL graphQL;
//解析json数据 因为graphql从前端传来的数据是json字符串
private static ObjectMapper MAPPER=new ObjectMapper();
@GetMapping
@ResponseBody
//graphql传来的是json字符串 必须用@RequestBody注解
public Map<String,Object> getQuery(@RequestBody String query) throws IOException {
return parseQuery(query);
}
@PostMapping
@ResponseBody
public Map<String,Object> postQuery(@RequestParam String query) throws IOException {
return parseQuery(query);
}
//解析前端传来的json字符串
public Map<String,Object> parseQuery(String query) throws IOException {
JsonNode jsonNode = MAPPER.readTree(query);
//graphql传来的json字符串中查询条件在"query"下
if(jsonNode.has("query")){
return graphQL.execute(jsonNode.get("query").textValue()).toSpecification();
}
Map<String, Object> error = new HashMap<>();
error.put("status", 500);
error.put("msg", "查询出错");
return error;
}
}
5.查询类型中有多个查询对象
- 什么是有多个查询对象
#定义查询
schema {
query: UserQuery
}
#定义查询类型(里面查找和返回的可以是多个对象)
type UserQuery {
#对象一
User(id:Long) : User
#对象二
UsersList(page:Int, pageSize:Int) : TableResult
#对象三
IndexAdList:IndexAdResult
}
#定义查询条件中对象类型
type User {
id:Long!
name:String
age:Int
}
type TableResult{
list:[User]
pagination:Pagination
}
type Pagination{
current:Int
pageSize:Int
total:Int
}
type IndexAdResult{
list:[IndexAdResultData]
}
type IndexAdResultData{
original:String
}
- 存在弊端:DataFetcher增多 不好管理
- 解决方案:定义一个DataFetcher的接口 所有DataFetcher都实现该接口 然后所有该DataFetcher的实现类 都注入到GraphqlProvider中
- MyDataFetcher(接口)
public interface MyDataFetcher {
//查询类型中对象的名称
String fieldName();
//通过DataFetchingEnvironment 获取查询条件对象的参数值 并返回查询到的数据
Object dataFetcher(DataFetchingEnvironment environment);
}
- UserDataFetcher(MyDataFetcher实现类 对应UserQuery中User对象)
@Component
public class UserDataFetcher implements MyDataFetcher {
@Autowired
private UserService userService;
@Override
public String fieldName() {
//对应查询条件对象User
return "User";
}
@Override
public Object dataFetcher(DataFetchingEnvironment environment) {
//获取User对象查询参数id
Long id = environment.getArgument("id");
return this.userService.queryUserById(id);
}
}
- UserListDataFetcher(MyDataFetcher实现类 对应UserQuery中UsersList对象)
@Component
public class UserListDataFetcher implements MyDataFetcher {
@Autowired
private UserService userService;
@Override
public String fieldName() {
//对应查询条件对象UsersList
return "UsersList";
}
@Override
public Object dataFetcher(DataFetchingEnvironment environment) {
//获取UsersList对象查询参数page
Integer page = environment.getArgument("page");
if(null == page){
page = 1;
}
//获取UsersList对象查询参数pageSize
Integer pageSize = environment.getArgument("pageSize");
if(null == pageSize){
pageSize = 5;
}
return this.userService.queryList(null, page, pageSize);
}
}
- 此处省略IndexAdList对象的DataFetcher
- GraphqlProvider(重新实现buildWiring方法)
@Component
public class GraphqlProvider {
private GraphQL graphQL;
//注入MyDataFetcher的所有实现类
@Autowired
private List<MyDataFetcher> myDataFetchers;
@Bean
private GraphQL graphQL(){
return this.graphQL;
}
//实现对GraphQL对象的初始化
@PostConstruct
public void init() throws FileNotFoundException {
File file = ResourceUtils.getFile("classpath:user.graphqls");
this.graphQL = GraphQL.newGraphQL(buildGraphQLSchema(file)).build();
}
private GraphQLSchema buildGraphQLSchema(File file) {
TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(file);
return new SchemaGenerator().makeExecutableSchema(typeRegistry, buildWiring());
}
private RuntimeWiring buildWiring() {
return RuntimeWiring.newRuntimeWiring()
.type("UserQuery", builder -> {
//遍历myDataFetcher的所有实现类
for (MyDataFetcher myDataFetcher : myDataFetchers) {
//获取查询条件对象的名字
builder.dataFetcher(myDataFetcher.fieldName(),
//调用不同的myDataFetcher实现类的dataFetcher方法 返回查询到的数据
environment -> myDataFetcher.dataFetcher(environment));
}
return builder;
}
)
.build();
}
}
推荐阅读
-
Springboot整合微信小程序实现登录与增删改查
-
手把手教你Dubbo与SpringBoot常用两种方式整合
-
SpringBoot整合log4j日志与HashMap的底层原理解析
-
SpringBoot中的定时任务与Quartz的整合
-
springBoot 文件上传与下载整合
-
SpringBoot 整合 Shiro 密码登录与邮件验证码登录功能(多 Realm 认证)
-
SpringBoot与tk.mybatis整合遇到的问题
-
springboot 与 shiro 整合 (简洁版)
-
Https双向验证与Springboot整合测试-人来人往我只认你
-
荐 springboot整合swagger2以及swagger2的介绍与Spring Security的整合使用