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字段顺序如下:

  1. method (*fieldArgs [, DataFetchingEnvironment])
  2. method is(*fieldArgs [, DataFetchingEnvironment]), only if the field returns a Boolean
  3. method get(*fieldArgs [, DataFetchingEnvironment])
  4. method getField(*fieldArgs [, DataFetchingEnvironment])
  5. field

例如:

上述type Book中的name字段的映射顺序为:

  1. 在java Book类中找name(参数)方法。
  2. 如果Book类中没有name(参数)方法,则继续找isName(参数)方法。
  3. 如果Book中没有isName(参数)方法,则继续在Book中找getName(参数)方法。
  4. 如果Book中没有getName()方法,则继续在Book中找getFieldName(参数)方法。
  5. 如果Book中没有getFieldName(参数)方法,在继续在Book中找name成员变量。
  6. 如果Book中没有name成员变量,则报错。

graphql type中的字段映射Resolver的优先级:

  1. method (dataClassInstance, *fieldArgs [, DataFetchingEnvironment])
  2. method is(dataClassInstance, *fieldArgs [, DataFetchingEnvironment]), only if the field returns a Boolean
  3. method get(dataClassInstance, *fieldArgs [, DataFetchingEnvironment])
  4. 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的几种方式的更多相关文章

  1. Spring Boot中使用Swagger2构建API文档

    程序员都很希望别人能写技术文档,自己却很不愿意写文档.因为接口数量繁多,并且充满业务细节,写文档需要花大量的时间去处理格式排版,代码修改后还需要同步修改文档,经常因为项目时间紧等原因导致文档滞后于代码 ...

  2. 从零开始的Spring Boot(2、在Spring Boot中整合Servlet、Filter、Listener的方式)

    在Spring Boot中整合Servlet.Filter.Listener的方式 写在前面 从零开始的Spring Boot(1.搭建一个Spring Boot项目Hello World):http ...

  3. Spring Boot 定义系统启动任务,你会几种方式?

    在 Servlet/Jsp 项目中,如果涉及到系统任务,例如在项目启动阶段要做一些数据初始化操作,这些操作有一个共同的特点,只在项目启动时进行,以后都不再执行,这里,容易想到web基础中的三大组件( ...

  4. 在Spring Boot快捷地读取文件内容的若干种方式

    引言: 在Spring Boot构建的项目中,在某些情况下,需要自行去读取项目中的某些文件内容,那该如何以一种轻快简单的方式读取文件内容呢?  基于ApplicationContext读取 在Spri ...

  5. spring boot 在不同环境下读取不同配置文件的一种方式

    在工程中,通常有根据不同的环境读取不同配置文件的需求,对于spring boot 来说,默认读取的是application.yml 或者 application.properties.为了区分不同的环 ...

  6. spring boot项目获取application配置文件参数的两种方式

    前言:了解过spring boot这个技术的,应该知道spring boot的核心配置文件application.properties,当然也可以通过注解自定义配置文件**.properties的信息 ...

  7. Spring Boot中yml配置文件Map集合注入及使用方式

    yml配置文件 maps: "{key1: 'value1', key2: 'value2'}" java中 @Value("#{${maps}}") priv ...

  8. Spring MVC中页面向后台传值的几种方式

    在学习 Spring Mvc 过程中,有必要来先了解几个关键参数:   @Controller:         在类上注解,则此类将编程一个控制器,在项目启动 Spring 将自动扫描此类,并进行对 ...

  9. Spring框架中获取连接池常用的四种方式

    1:DBCP数据源 DBCP类包位于 /lib/jakarta-commons/commons-dbcp.jar,DBCP是一个依赖Jakarta commons-pool对象池机制的数据库连接池,所 ...

随机推荐

  1. centos7之iptables与firewalld

    保障数据的安全性是继保障数据的可用性之后最为重要的一项工作.防火墙作为公网 与内网之间的保护屏障,在保障数据的安全性方面起着至关重要的作用. firewalld与iptables iptables f ...

  2. loadrunner--TPS和平均事务响应时间

    TPS就是每秒事务数,但是事务是基于虚拟用户数的,假如1个虚拟用户在1秒内完成1笔事务,那么TPS明显就是1:如果 某笔业务响应时间是1ms,那么1个用户在1秒内能完成1000笔事务,TPS就是100 ...

  3. Alluxio/Tachyon如何发挥lineage的作用?

    在Spark的RDD中引入过lineage这一概念.指的是RDD之间的依赖.而Alluxio则使用lineage来表示文件之间的依赖.在代码层面,指的是fileID之间的依赖. 代码中的注释指出: * ...

  4. abp 的坑

    多数据库连接问题 viewmodel的验证问题 发布后,一段查找sln查找代码无法适用生产环境问题 多语言问题,默认中文设置与模板配置文件不统一

  5. 深入理解line-height与vertical-align(1)

    http://www.cnblogs.com/xiaohuochai/p/5271217.html

  6. 【Log】logback的配置和使用(一)

    logback介绍 Logback是由log4j创始人设计的又一个开源日志组件.logback当前分成三个模块:logback-core,logback- classic和logback-access ...

  7. document.body和document.documentElement区别

    1.document.documentElement表示文档节点树的根节点,即<html> document.body是body节点 2. 页面具有 DTD,或者说指定了 DOCTYPE ...

  8. KbmMW 4.5 发布

    We are happy to announce the release of kbmMW v. 4.50.00 Professional, Enterprise and CodeGear Editi ...

  9. 动词的时态(Les temps du verbe )

    在开始讲解直陈式现在时的主要用法之前,我们有必要先搞清楚两个基本概念:▶语式(mode):语式是动词表达动作的方式.一个动作,可以作为实在的事表达出来,也可以作为希望或单纯设想的事表达出来,法语动词共 ...

  10. 浅谈webuploader上传文件

    官网:http://c7.gg/fw4sn 案例: 文件上传进度 // 文件上传过程中创建进度条实时显示. uploader.on( 'uploadProgress', function( file, ...