欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

GraphQL介绍与SpringBoot整合GraphQL

程序员文章站 2022-05-17 08:48:07
...

一.查询规范

  • 参考官网文档:https://graphql.cn/learn/queries/#variables

1.字段(Fields): 在GraphQL的查询中,请求结构中包含了所预期结果的结构,这个就是字段。并且响应的结构和请求结构基本一致

  • 语法
    {
        查询对象{
            查询字段1
            查询字段2
        }
    }
    
  • 例子:查询所有hero对象的name字段
    GraphQL介绍与SpringBoot整合GraphQL

2.参数(Arguments):在查询数据时,离不开传递参数,在GraphQL的查询中也是可以传递参数

  • 语法
    {
        查询对象(参数名:"参数值"){
            查询字段1
            查询字段2
        }
    }
    
  • 例子:查询所有id=1000的hero对象 并只返回name字段
    GraphQL介绍与SpringBoot整合GraphQL

3.别名(Aliases):如果一次查询多个相同对象,但是值不同,这个时候就需要起别名了,否则json的语法就不能通过了。

  • 语法
    {
        对象别名1:查询对象(参数名1:参数值1){
            查询字段1
            查询字段2
        }
        对象别名2:查询对象(参数名2:参数值2){
            查询字段1
            查询字段2
        }
    }
    
  • 例子:同时查询 “episode=EMPIRE” 和 “episode=JEDI” 的hero对象
    GraphQL介绍与SpringBoot整合GraphQL

4.片段(Fragments):如果查询多个对象的 但查询的属性如果相同,可以采用片段的方式进行简化定义。

  • 语法
    {
        对象别名1:查询对象(参数名1:参数值1){
            片段名称
        }
        对象别名2:查询对象(参数名2:参数值2){
            片段名称
        }
    }
    
    fragment 片段名称 on Character{
        字段1
        字段2
    }
    
    
  • 例子
    GraphQL介绍与SpringBoot整合GraphQL

二.GraphQL的Schema 和类型规范

  • 参考官网文档:http://graphql.cn/learn/schema/
  • 实际GraphQL是Restful的改进 同样是以对象作为查询条件 也将该对象作为返回数据 拓展功能有
    • 在查询时可以指定用该对像的哪些字段的值查询
    • 在返回对象时可以指定返回对象部分字段的值
    • 可以查询根据多个不同查询条件查询多个对象(通过别名)
    • 支持对象嵌套

1.Schema定义结构

#定义查询 
schema { 
	query: 查询类型名称
}

#定义查询的类型
type 查询类型 {  
	#设置对象 该对象可用于查询与返回
	查询对象名称(字段名:字段类型) : 对象类型
}

#定义对象类型(与java中实体类一般相同)
type 对象类型 {
	字段1:类型!  # !表示该属性是非空项 
	字段2:类型
	字段3:类型
}

2.标量类型(Scalar Types)

  1. Int :有符号 32 位整数。
  2. Float :有符号双精度浮点值。
  3. String :UTF‐8 字符序列。
  4. Boolean : true 或者 false 。
  5. 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中
  1. MyDataFetcher(接口)
public interface MyDataFetcher {
	//查询类型中对象的名称
    String fieldName();
	
	//通过DataFetchingEnvironment 获取查询条件对象的参数值 并返回查询到的数据
    Object dataFetcher(DataFetchingEnvironment environment);
}

  1. 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);
    }
}
  1. 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);
    }
}

  1. 此处省略IndexAdList对象的DataFetcher
  2. 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();
    }


}