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/