记录初学Spring boot中使用GraphQL编写API的几种方式
Spring boot+graphql
一、使用graphql-java-tools方式
<dependency>
<groupId>com.graphql-java-kickstart</groupId>
<artifactId>graphql-java-tools</artifactId>
<version>5.6.0</version>
</dependency>
<dependency>
<groupId>com.graphql-java-kickstart</groupId>
<artifactId>graphiql-spring-boot-starter</artifactId>
<version>5.0.4</version>
</dependency>
schema.graphqls
type Query {
books: [Book!]
}
type Book {
id: Int!
name: String!
author: Author!
}
type Author {
id: Int!
name: String!
}
对应的java class
class Book {
private int id;
private String name;
private int authorId;
// constructor
// getId
// getName
// getAuthorId
}
class Author {
private int id;
private String name;
// constructor
// getId
// getName
}
Book-Resolver
class BookResolver implements GraphQLResolver<Book> {
private AuthorRepository authorRepository;
public BookResolver(AuthorRepository authorRepository) {
this.authorRepository = authorRepository;
}
public Author author(Book book) {
return authorRepository.findById(book.getAuthorId());
}
}
Query-Resolver
class Query implements GraphQLQueryResolver {
private BookRepository bookRepository;
public Query(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
public List<Book> books() {
return bookRepository.findAll();
}
}
Type Query 没有对应的java class,如果type 中的所有字段和java class成员变量一致,则该type可以不用定义Resolver.
graphql type中的字段映射Java class字段的优先级
对于graphql objectType中的字段映射为java class字段顺序如下:
- method (*fieldArgs [, DataFetchingEnvironment])
- method is(*fieldArgs [, DataFetchingEnvironment]), only if the field returns a Boolean
- method get(*fieldArgs [, DataFetchingEnvironment])
- method getField(*fieldArgs [, DataFetchingEnvironment])
- field
例如:
上述type Book中的name字段的映射顺序为:
- 在java Book类中找name(参数)方法。
- 如果Book类中没有name(参数)方法,则继续找isName(参数)方法。
- 如果Book中没有isName(参数)方法,则继续在Book中找getName(参数)方法。
- 如果Book中没有getName()方法,则继续在Book中找getFieldName(参数)方法。
- 如果Book中没有getFieldName(参数)方法,在继续在Book中找name成员变量。
- 如果Book中没有name成员变量,则报错。
graphql type中的字段映射Resolver的优先级:
- method (dataClassInstance, *fieldArgs [, DataFetchingEnvironment])
- method is(dataClassInstance, *fieldArgs [, DataFetchingEnvironment]), only if the field returns a Boolean
- method get(dataClassInstance, *fieldArgs [, DataFetchingEnvironment])
- method getField(dataClassInstance, *fieldArgs [, DataFetchingEnvironment])
注:Resolver的映射优先级高于Java Class,首先在Resolver中查找,如果没找到,才会在Java class中查找 :例如上述type Book中的author字段,会首先映射为BookResolver重的author(Book)方法。
解析schema.graphqls,创建Graphql对象:
import com.coxautodev.graphql.tools.SchemaParser;
GraphQLSchema schema = SchemaParser.newParser().file("schema.graphqls")
.resolvers(new QueryResolver(), new BookResolver())
.build()
.makeExecutableSchema();
GraphQL graphQL = GraphQL.newGraphQL(schema).build();
// 执行查询
ExecutionResult result = graphQL.execute(query);
Map<String, Object> map = result.toSpecification();
二、不使用Resolver
schema.graphqls:
type Query {
bookById(id: ID): Book
}
type Book {
id: ID
name: String
pageCount: Int
author: Author
}
type Author {
id: ID
firstName: String
lastName: String
}
加载schema.graphqls,创建GraphQL对象:
import graphql.schema.idl.SchemaParser;
@Value("classpath:schema.graphqls")
Resource resource;
@PostConstruct
private void loadSchema() throws Exception {
File schemaFile = resource.getFile();
GraphQLSchema schema = buildSchema(schemaFile);
graphQL = GraphQL.newGraphQL(schema).build();
}
private GraphQLSchema buildSchema(File file) throws Exception {
TypeDefinitionRegistry registry = new SchemaParser().parse(file);
RuntimeWiring runtimeWiring = buildWiring();
SchemaGenerator schemaGenerator = new SchemaGenerator();
return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);
}
private RuntimeWiring buildWiring() {
return RuntimeWiring.newRuntimeWiring()
// 为每个graphql type的字段提供DataFetcher
.type(newTypeWiring("Query")
.dataFetcher("bookById", graphQLDataFetchers.getBookByIdDataFetcher()))
.type(newTypeWiring("Book")
.dataFetcher("author", graphQLDataFetchers.getAuthorDataFetcher()))
.build();
}
DataFetcher:
@Component
public class GraphQLDataFetchers {
private static List<Map<String, String>> books = Arrays.asList(
ImmutableMap.of("id", "book-1",
"name", "Harry Potter and the Philosopher's Stone",
"pageCount", "223",
"authorId", "author-1"),
ImmutableMap.of("id", "book-2",
"name", "Moby Dick",
"pageCount", "635",
"authorId", "author-2"),
ImmutableMap.of("id", "book-3",
"name", "Interview with the vampire",
"pageCount", "371",
"authorId", "author-3")
);
private static List<Map<String, String>> authors = Arrays.asList(
ImmutableMap.of("id", "author-1",
"firstName", "Joanne",
"lastName", "Rowling"),
ImmutableMap.of("id", "author-2",
"firstName", "Herman",
"lastName", "Melville"),
ImmutableMap.of("id", "author-3",
"firstName", "Anne",
"lastName", "Rice")
);
public DataFetcher getBookByIdDataFetcher() {
return dataFetchingEnvironment -> {
String bookId = dataFetchingEnvironment.getArgument("id");
return books
.stream()
.filter(book -> book.get("id").equals(bookId))
.findFirst()
.orElse(null);
};
}
public DataFetcher getAuthorDataFetcher() {
return dataFetchingEnvironment -> {
Map<String,String> book = dataFetchingEnvironment.getSource();
String authorId = book.get("authorId");
return authors
.stream()
.filter(author -> author.get("id").equals(authorId))
.findFirst()
.orElse(null);
};
}
}
注:这种方式,并不要求一定要提供type对应的java class,只要在对应的DataFetcher中返回符合type的数据格式即可
三、方式三,不使用graphql-java-tools
不定义schema.graphqls,以编码的方式创建graphql type。
定义graphql type:
GraphQLObjectType fooType = newObject()
.name("Foo")
.field(newFieldDefinition()
.name("bar")
.type(GraphQLString))
.build();
上述代码相当于使用schema方式创建了如下graphql type:
type Foo {
bar: String
}
为类型的field指定DataFetcher:
DataFetcher<Foo> fooDataFetcher = environment -> {
// environment.getSource() is the value of the surrounding
// object. In this case described by objectType
Foo value = perhapsFromDatabase(); // Perhaps getting from a DB or whatever
return value;
}
GraphQLObjectType objectType = newObject()
.name("ObjectType")
.field(newFieldDefinition()
.name("foo")
.type(GraphQLString)
.dataFetcher(fooDataFetcher))
.build();
完整的代码:
// 定义一个type Query
/**
* 相当于 type QueryType{ hello: String }
*/
GraphQLObjectType queryType = newObject()
.name("QueryType")
.field(newFieldDefinition()
.name("hello")
.type(GraphQLString)
.dataFetcher(new StaticDataFetcher("world!"))
.build();
// 创建GraphQLSchema
GraphQLSchema schema = GraphQLSchema.newSchema()
.query(queryType)
.build();
// Make the schema executable
GraphQL executor = GraphQL.newGraphQL(graphQLSchema).build();
ExecutionResult executionResult = executor.execute("{hello}");
四、参考链接
https://www.graphql-java.com/tutorials/getting-started-with-spring-boot/
https://www.graphql-java-kickstart.com/tools/
https://www.graphql-java.com/documentation/v11/
记录初学Spring boot中使用GraphQL编写API的几种方式的更多相关文章
- Spring Boot中使用Swagger2构建API文档
程序员都很希望别人能写技术文档,自己却很不愿意写文档.因为接口数量繁多,并且充满业务细节,写文档需要花大量的时间去处理格式排版,代码修改后还需要同步修改文档,经常因为项目时间紧等原因导致文档滞后于代码 ...
- 从零开始的Spring Boot(2、在Spring Boot中整合Servlet、Filter、Listener的方式)
在Spring Boot中整合Servlet.Filter.Listener的方式 写在前面 从零开始的Spring Boot(1.搭建一个Spring Boot项目Hello World):http ...
- Spring Boot 定义系统启动任务,你会几种方式?
在 Servlet/Jsp 项目中,如果涉及到系统任务,例如在项目启动阶段要做一些数据初始化操作,这些操作有一个共同的特点,只在项目启动时进行,以后都不再执行,这里,容易想到web基础中的三大组件( ...
- 在Spring Boot快捷地读取文件内容的若干种方式
引言: 在Spring Boot构建的项目中,在某些情况下,需要自行去读取项目中的某些文件内容,那该如何以一种轻快简单的方式读取文件内容呢? 基于ApplicationContext读取 在Spri ...
- spring boot 在不同环境下读取不同配置文件的一种方式
在工程中,通常有根据不同的环境读取不同配置文件的需求,对于spring boot 来说,默认读取的是application.yml 或者 application.properties.为了区分不同的环 ...
- spring boot项目获取application配置文件参数的两种方式
前言:了解过spring boot这个技术的,应该知道spring boot的核心配置文件application.properties,当然也可以通过注解自定义配置文件**.properties的信息 ...
- Spring Boot中yml配置文件Map集合注入及使用方式
yml配置文件 maps: "{key1: 'value1', key2: 'value2'}" java中 @Value("#{${maps}}") priv ...
- Spring MVC中页面向后台传值的几种方式
在学习 Spring Mvc 过程中,有必要来先了解几个关键参数: @Controller: 在类上注解,则此类将编程一个控制器,在项目启动 Spring 将自动扫描此类,并进行对 ...
- Spring框架中获取连接池常用的四种方式
1:DBCP数据源 DBCP类包位于 /lib/jakarta-commons/commons-dbcp.jar,DBCP是一个依赖Jakarta commons-pool对象池机制的数据库连接池,所 ...
随机推荐
- css简单分页
html代码 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <ti ...
- PAT 1065 单身狗(25)(STL-map+思路+测试点分析)
1065 单身狗(25 分) "单身狗"是中文对于单身人士的一种爱称.本题请你从上万人的大型派对中找出落单的客人,以便给予特殊关爱. 输入格式: 输入第一行给出一个正整数 N(≤ ...
- 1.struts 防止表单重复提交 2. 拦截器
1. 使用struts 防止表单提交 时, form 表单必须使用struts标签库编写,如<s:form/> 等,而不是html标签 2. 拦截器是struts2的核心. interc ...
- arm家族小检阅
- VC6.0 OpenGL环境配置及编程基础
1.一般情况下VC并不携带glut,需要到opengl官网下载,下载地址 http://www.opengl.org/resources/libraries/glut/glut37.zip 解压后 打 ...
- 2018.09.15 hdu3018Ant Trip(欧拉路)
传送门 显然答案等于各个连通分量的笔画数之和. 因此我们dfs每个连通分量计算对答案的贡献. 对于一个连通分量,如果本来就有欧拉回路那么只需要一笔. 否则需要寄点数/2那么多笔才能画完. 知道这个结论 ...
- A标签中传递的中文参数到Servlet 后台request.getParameter()接收时出现中文乱码
package util; import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletRequ ...
- 状态机中的RAM注意的问题--减少扇出的办法
可能我不会抓紧时间,所以做事老是很慢.最近在整维特比译码过程深感自己有这样的毛病. 每天会有一点进展,但是却是一天的时间,感觉别人都做起事情来很快.可能这个东西有点难,做 不做得出来都不要紧,但我的想 ...
- Java(Android)线程池[转]
介绍new Thread的弊端及Java四种线程池的使用,对Android同样适用.本文是基础篇,后面会分享下线程池一些高级功能. 1.new Thread的弊端执行一个异步任务你还只是如下new T ...
- Shell编程-07-Shell中的case语句
目录 基本语法 case示例 case语句总结 case语句相当于多分支的if/elif/else语句,而在使用case会让脚本看起来更简单工整.在case语句中,程序会将获取到的值与case ...