hello,大叫好,我是小黑,又和大家見面啦~
今天我們來繼續(xù)學(xué)習(xí) Spring Boot GraphQL 實(shí)戰(zhàn),我們使用的框架是 https://github.com/graphql-java-kickstart/graphql-spring-boot
項(xiàng)目 github 地址:https://github.com/shenjianeng/graphql-spring-boot-example
Query(查詢)
帶參數(shù)的查詢
首先,在 classpath 下創(chuàng)建 graphqls 文件:
type Book{
id:ID!
name:String!
}
type Query{
# 根據(jù) id 查詢 book,參數(shù)名為 id,參數(shù)類型的 ID 類型,結(jié)果返回 book
getBookById(id:ID!):Book
}
創(chuàng)建一個(gè) Spring Bean,此處需要實(shí)現(xiàn) GraphQLQueryResolver 接口,并在該類中自定義一個(gè)方法來映射 graphqls 文件中的查詢。
@Data
public class Book {
private int id;
private String name;
}
@Component
public class BookGraphQLQueryResolver implements GraphQLQueryResolver {
public Book getBookById(int id) {
Book book = new Book();
book.setId(id);
book.setName("這邊書沒有書名");
return book;
}
}
復(fù)合字段查詢
需求:每本書都有作者,在查詢書本信息時(shí),有時(shí)需要返回作者信息。
# 定義 Author 數(shù)據(jù)類型結(jié)構(gòu)
type Author{
id:ID!
name:String!
}
type Book{
id:ID!
name:String!
# 增加 author 字段,數(shù)據(jù)類型為 Author
author:Author
}
type Query{
# 根據(jù) id 查詢 book,參數(shù)名為 id,參數(shù)類型的 ID 類型,結(jié)果返回 book
getBookById(id:ID!):Book
}
再看一下此時(shí)我們的 Java Bean:
@Data
public class Author {
private UUID id;
private String name;
}
@Data
public class Book {
private long id;
private String name;
}
看仔細(xì)哦,Book 類中并沒有 author 字段,Book 中 author 信息將由 graphql.kickstart.tools.GraphQLResolver 來提供。
@Slf4j
@Component
public class BookGraphQLResolver implements GraphQLResolver<Book> {
public Author author(Book book) {
log.info("book id :{} query author info", book.getId());
Author author = new Author();
author.setId(UUID.randomUUID());
author.setName(String.format("我是[%s]的作者", book.getName()));
return author;
}
}
ok,讓我們啟動(dòng)服務(wù),訪問 http://localhost:8080/graphiql

而當(dāng)客戶端不需要 author 信息時(shí),服務(wù)端就不會(huì)執(zhí)行 BookGraphQLResolver#author,真正做到了使得客戶端能夠準(zhǔn)確地獲得它需要的數(shù)據(jù),而且沒有任何冗余。
(ps:如果你是服務(wù)端開發(fā),你會(huì)怎么實(shí)現(xiàn)呢?是給客戶端提供一個(gè)接口返回 book 和 author 信息,還是給客戶端提供兩個(gè)不同的接口呢?)

Mutation(變更)
在 graphqls 文件中,使用 Query 來定義查詢接口,使用 Mutation 可以定義變更數(shù)據(jù)的操作。
type Mutation{
createBook(id:ID!,name:String!):Book
}
上述 graphqls 文件中定義了一個(gè) createBook 的方法,參數(shù)列表為 id 和 name ,方法返回創(chuàng)建的 Book 對(duì)象。
與之對(duì)應(yīng)的 Java 代碼如下:
@Component
public class BookGraphQLMutationResolver implements GraphQLMutationResolver {
public Book createBook(int id, String name) {
Book book = new Book();
book.setId(id);
book.setName(name);
return book;
}
}
BookGraphQLMutationResolver 實(shí)現(xiàn)了 graphql.kickstart.tools.GraphQLMutationResolver 接口,表明當(dāng)前類中的方法用來映射 graphqls 文件中的 Mutation。

Input Types
當(dāng) Mutation 中請(qǐng)求參數(shù)特別多時(shí),我們可以使用 Input Types 來優(yōu)化代碼。
type Mutation{
createBook(id:ID!,name:String!):Book
create(bookInput:BookInput!):Book
}
input BookInput{
id:ID!
name:String!
}
同理,我們也需求在 BookGraphQLMutationResolver 中添加對(duì)應(yīng)的方法來映射。
@Component
public class BookGraphQLMutationResolver implements GraphQLMutationResolver {
// ...省略其他代碼
public Book create(BookInput input) {
Book book = new Book();
book.setId(input.getId());
book.setName(input.getName());
return book;
}
}
客戶端請(qǐng)求代碼如下:

自定義標(biāo)量類型
在 GraphQL 中自帶一些默認(rèn)標(biāo)量類型:
Int:有符號(hào) 32 位整數(shù)Float:有符號(hào)雙精度浮點(diǎn)值String:UTF‐8 字符序列Boolean:true或者falseID:ID 標(biāo)量類型表示一個(gè)唯一標(biāo)識(shí)符,通常用以重新獲取對(duì)象或者作為緩存中的鍵。ID 類型使用和 String 一樣的方式序列化
使用 graphql-java-extended-scalars 庫
在 Java 這個(gè)生態(tài)中,我們可以引入下面這個(gè)庫來幫助我們很方便的進(jìn)行擴(kuò)展:
https://github.com/graphql-java/graphql-java-extended-scalars
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-extended-scalars</artifactId>
<version>15.0.0</version>
</dependency>
graphql-java-extended-scalars 中具體擴(kuò)展了哪些標(biāo)量類型,我們都可以在 graphql.scalars.ExtendedScalars 類中找到。
(ps:一個(gè)小技巧,s 結(jié)尾的類一般都是工具類)

如何使用呢?
- 向 Spring 容器中注冊(cè)自定義標(biāo)量
- 在 graphqls 文件中聲明要使用的自定義標(biāo)量
- 直接使用即可
相關(guān)示例代碼如下:
@Configuration
public class CustomScalarTypeConfig {
@Bean
public GraphQLScalarType graphQLLong() {
return ExtendedScalars.GraphQLLong;
}
}
scalar Long
type Book{
id:ID!
name:String!
# 增加 author 字段,數(shù)據(jù)類型為 Author
author:Author
totalPageSize:Long
}
使用 GraphQLScalarType 自定義標(biāo)量類型
我們可以參考 graphql.scalars.java.JavaPrimitives#GraphQLLong 的實(shí)現(xiàn)來自定標(biāo)量類型。
@Bean
public GraphQLScalarType graphQLDate() {
return GraphQLScalarType
.newScalar()
.name("Date")
.description("Date 類型")
.coercing(new Coercing<Date, String>() {
@Override
public String serialize(Object dataFetcherResult) throws CoercingSerializeException {
return new SimpleDateFormat(DATE_FORMAT_PATTERN_DEFAULT).format((Date) dataFetcherResult);
}
@Override
public Date parseValue(Object input) throws CoercingParseValueException {
if (input instanceof String) {
try {
return new SimpleDateFormat(DATE_FORMAT_PATTERN_DEFAULT).parse((String) input);
} catch (ParseException e) {
throw new CoercingParseValueException(e);
}
}
throw new CoercingParseValueException(
"Expected a 'String' but was '" + Kit.typeName(input) + "'."
);
}
@Override
public Date parseLiteral(Object input) throws CoercingParseLiteralException {
if (!(input instanceof StringValue)) {
throw new CoercingParseLiteralException(
"Expected AST type 'StringValue' but was '" + typeName(input) + "'."
);
}
try {
return new SimpleDateFormat(DATE_FORMAT_PATTERN_DEFAULT).parse(((StringValue) input).getValue());
} catch (ParseException e) {
throw new CoercingParseValueException(e);
}
}
})
.build();
}
DataFetcherResult
在 Resolver 中,我們可以使用 graphql.execution.DataFetcherResult 來包裝返回的結(jié)果,示例代碼如下:
@Component
public class BookGraphQLQueryResolver implements GraphQLQueryResolver {
public DataFetcherResult<Book> getBookById(int id) {
if (id <= 0) {
return DataFetcherResult
.<Book>newResult()
.error(new GenericGraphQLError("id 不能為負(fù)數(shù)"))
.build();
}
Book book = new Book();
book.setId(id);
book.setName("這邊書沒有書名");
return DataFetcherResult
.<Book>newResult()
.data(book)
.build();
}
}
下期預(yù)告
下期我們將使用 graphQL 來實(shí)現(xiàn)分頁,并介紹一些高級(jí)特性,例如:異步加載、全局異常處理等。感謝大家的關(guān)注和閱讀~~
更多學(xué)習(xí)參考資料:
https://www.graphql-java-kickstart.com/tools/schema-definition/#resolvers-and-data-classes