hello,大叫好,我是小黑,又和大家见面啦~
今天我们来继续学习 Spring Boot GraphQL 实战,我们使用的框架是 https://github.com/graphql-java-kickstart/graphql-spring-boot
项目 github 地址:https://github.com/shenjianeng/graphql-spring-boot-example
Query(查询)
带参数的查询
首先,在 classpath 下创建 graphqls 文件:
type Book{ id:ID! name:String!}type Query{ # 根据 id 查询 book,参数名为 id,参数类型的 ID 类型,结果返回 book getBookById(id:ID!):Book}
创建一个 Spring Bean,此处需要实现 GraphQLQueryResolver
接口,并在该类中自定义一个方法来映射 graphqls 文件中的查询。
@Datapublic class Book { private int id; private String name;}@Componentpublic class BookGraphQLQueryResolver implements GraphQLQueryResolver { public Book getBookById(int id) { Book book = new Book(); book.setId(id); book.setName("这边书没有书名"); return book; }}
复合字段查询
需求:每本书都有作者,在查询书本信息时,有时需要返回作者信息。
# 定义 Author 数据类型结构type Author{ id:ID! name:String!}type Book{ id:ID! name:String! # 增加 author 字段,数据类型为 Author author:Author}type Query{ # 根据 id 查询 book,参数名为 id,参数类型的 ID 类型,结果返回 book getBookById(id:ID!):Book}
再看一下此时我们的 Java Bean:
@Datapublic class Author { private UUID id; private String name;}@Datapublic class Book { private long id; private String name;}
看仔细哦,Book
类中并没有 author
字段,Book 中 author 信息将由 graphql.kickstart.tools.GraphQLResolver
来提供。
@Slf4j@Componentpublic 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,让我们启动服务,访问 src="https://cdn.jsdelivr.net/gh/shenjianeng/pictures/2020-12-19/1608362776229-image.png" alt="同时查询book和author" loading="lazy">
而当客户端不需要 author 信息时,服务端就不会执行 BookGraphQLResolver#author
,真正做到了使得客户端能够准确地获得它需要的数据,而且没有任何冗余。
(ps:如果你是服务端开发,你会怎么实现呢?是给客户端提供一个接口返回 book 和 author 信息,还是给客户端提供两个不同的接口呢?)
Mutation(变更)
在 graphqls 文件中,使用 Query 来定义查询接口,使用 Mutation 可以定义变更数据的操作。
type Mutation{ createBook(id:ID!,name:String!):Book}
上述 graphqls 文件中定义了一个 createBook
的方法,参数列表为 id
和 name
,方法返回创建的 Book
对象。
与之对应的 Java 代码如下:
@Componentpublic class BookGraphQLMutationResolver implements GraphQLMutationResolver { public Book createBook(int id, String name) { Book book = new Book(); book.setId(id); book.setName(name); return book; }}
BookGraphQLMutationResolver
实现了 graphql.kickstart.tools.GraphQLMutationResolver
接口,表明当前类中的方法用来映射 graphqls 文件中的 Mutation。
Input Types
当 Mutation 中请求参数特别多时,我们可以使用 Input Types 来优化代码。
type Mutation{ createBook(id:ID!,name:String!):Book create(bookInput:BookInput!):Book}input BookInput{ id:ID! name:String!}
同理,我们也需求在 BookGraphQLMutationResolver
中添加对应的方法来映射。
@Componentpublic class BookGraphQLMutationResolver implements GraphQLMutationResolver { // ...省略其他代码 public Book create(BookInput input) { Book book = new Book(); book.setId(input.getId()); book.setName(input.getName()); return book; }}
客户端请求代码如下:
自定义标量类型
在 GraphQL 中自带一些默认标量类型:
Int
:有符号 32 位整数Float
:有符号双精度浮点值String
:UTF‐8 字符序列Boolean
:true
或者false
ID
:ID 标量类型表示一个唯一标识符,通常用以重新获取对象或者作为缓存中的键。ID 类型使用和 String 一样的方式序列化
使用 graphql-java-extended-scalars 库
在 Java 这个生态中,我们可以引入下面这个库来帮助我们很方便的进行扩展:
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
中具体扩展了哪些标量类型,我们都可以在 graphql.scalars.ExtendedScalars
类中找到。
(ps:一个小技巧,s 结尾的类一般都是工具类)
如何使用呢?
- 向 Spring 容器中注册自定义标量
- 在 graphqls 文件中声明要使用的自定义标量
- 直接使用即可
相关示例代码如下:
@Configurationpublic class CustomScalarTypeConfig { @Bean public GraphQLScalarType graphQLLong() { return ExtendedScalars.GraphQLLong; }}
scalar Longtype Book{ id:ID! name:String! # 增加 author 字段,数据类型为 Author author:Author totalPageSize:Long}
使用 GraphQLScalarType 自定义标量类型
我们可以参考 graphql.scalars.java.JavaPrimitives#GraphQLLong
的实现来自定标量类型。
@Beanpublic 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
来包装返回的结果,示例代码如下:
@Componentpublic class BookGraphQLQueryResolver implements GraphQLQueryResolver { public DataFetcherResult<Book> getBookById(int id) { if (id <= 0) { return DataFetcherResult .<Book>newResult() .error(new GenericGraphQLError("id 不能为负数")) .build(); } Book book = new Book(); book.setId(id); book.setName("这边书没有书名"); return DataFetcherResult .<Book>newResult() .data(book) .build(); }}
下期预告
下期我们将使用 graphQL 来实现分页,并介绍一些高级特性,例如:异步加载、全局异常处理等。感谢大家的关注和阅读~~
更多学习参考资料:
https://www.graphql-java-kickstart.com/tools/schema-definition/#resolvers-and-data-classes
https://graphql.org/learn/schema/
原文转载:http://www.shaoqun.com/a/504291.html
auction:https://www.ikjzd.com/w/2311
五洲会海购:https://www.ikjzd.com/w/1068
SpringBootGraphQL实战系列第二篇,使用GraphQL实现增删改查。hello,大叫好,我是小黑,又和大家见面啦~今天我们来继续学习SpringBootGraphQL实战,我们使用的框架是https://github.com/graphql-java-kickstart/graphql-spring-boot项目github地址:https://github.com/shenjian
patpat:patpat
Zozo:Zozo
东部华侨城好不好玩?:东部华侨城好不好玩?
全球唯一"十星级酒店"大揭秘 弱爆五星级:全球唯一"十星级酒店"大揭秘 弱爆五星级
EPC:EPC
没有评论:
发表评论