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. 使用bcp工具对boost库裁剪

    有些时候,我们需要通过源代码来发布我们的产品,在使用了CI工具之后,一般我们要求每天对源码进行构建,以防止代码不可用了还不自知.如果我们使用了Boost库,我们就需要在构建的过程中将Boost同时构建 ...

  2. Halcon的编程语法与数据处理——第8讲

    1.跟其他语言不完全一致的表达符号 赋值符号  := 引号      ' ' (一律是单引号) 求商求余  /   % (一个整数除以另一个数,如何使商是实型的?即浮点型) 逻辑运算  and  or ...

  3. UNIX和类UNIX操作系统

  4. 用 npm 安装删除模块

    npm安装模块 [npm install xxx]利用 npm 安装xxx模块到当前命令行所在目录: [npm install -g xxx]利用npm安装全局模块xxx: 本地安装时将模块写入pac ...

  5. Vue.js 与 Laravel 分离

    首先表示折腾了十来天的php-laravel框架和vue的结合开发又偏前端实在是太大的阻碍,首先laravel的机制就是写完路由router再加载blade模板的.如果要在laravel工程里面加载一 ...

  6. 使用delphi 开发多层应用(二十二)使用kbmMW 的认证管理器

    从kbmmw 4.4 开始,增加了认证管理器,这个比原来的简单认证提供了更多的功能.细化了很多权限操作. 今天对这一块做个介绍. 要做一个认证管理,大概分为以下5步: 1.  定义你要保护的资源,一般 ...

  7. 2018.09.15 poj1041John's trip(欧拉路输出方案)

    传送门 一个欧拉路输出方案的板子题. 竟然难在读入233. 代码: #include<iostream> #include<cstdio> #include<cstrin ...

  8. Django的路由层(1)

    https://www.cnblogs.com/yuanchenqi/articles/8876685.html URL配置(URLconf)就像Django 所支撑网站的目录.它的本质是URL与要为 ...

  9. Linux服务器部署系列之四—DHCP篇

    DHCP服务器的配置是Linux服务器配置中最简单的服务之一,网上也有很多相关文档,不过大部分都只是讲解了配置.虽然我这篇文档也不一定很完善,不过我还是希望能够尽量说得明白一些,同时也希望大家能够提供 ...

  10. 61 origin授控于MATLAB

    官方教程:http://www.originlab.com/forum/topic.asp?TOPIC_ID=22339 学习自白东升老师originPRO8.0教程. 我用的是origin pro2 ...