如何使用GraphQL
本篇内容介绍了“如何使用GraphQL”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
GraphQL 自带一组默认标量类型
Int:有符号 32 位整数。
Float:有符号双精度浮点值。
String:UTF‐8 字符序列。
Boolean:true 或者 false。
ID:ID 标量类型表示一个唯一标识符,通常用以重新获取对象或者作为缓存中的键。ID 类型使用和 String 一样的方式序列化;然而将其定义为 ID 意味着并不需要人类可读型。
代码见gitee仓库example
GraphQL操作类型
query 查询
mutation 突变(理解为修改即可)
subscription 脚本
dgs自定义scalar
实现接口Coercing
添加注解@DgsScalar 示例
@DgsScalar(name = "Date") public class DateCoercing implements Coercing<date, string> { private static final String PATTERN = "yyyy-MM-dd HH:mm:ss"; @Override public String serialize(Object o) throws CoercingSerializeException { if (o instanceof Date && Objects.nonNull(o)) { Date date = (Date)o; SimpleDateFormat sdf = new SimpleDateFormat(PATTERN); return sdf.format(date); } return null; } @Override public Date parseValue(Object o) throws CoercingParseValueException { if (o instanceof Date && Objects.nonNull(o)) { return (Date)o; } else { throw new CoercingParseValueException("type is not date "); } } @Override public Date parseLiteral(Object o) throws CoercingParseLiteralException { if (o instanceof String && Objects.nonNull(o)) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern(PATTERN); LocalDateTime localDateTime = LocalDateTime.parse(o.toString(), formatter); return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); } return null; } } .graphql配置 type Show { title: String releaseYear: Int now: Date } scalar Date
postman访问
别名
当两个查询名相同时会引发冲突,这时可以使用别名获取不同的结果
{ zhangsan: hello(name: "张三"), lisi: hello(name: "李四") } 输出: { "data": { "zhangsan": "hello:张三!", "lisi": "hello:李四!" } }
片段(fragment)
片段主要用于将多个相同的字段进行封装,我们在实际开发中会遇到返回结果中很多字段名名相同,使用==fragment==在.graphql文件中定义
{ golang: shows (title: "golang"){ ...showFields }, java: shows (title: "java"){ ...showFields } } # 定义片段 fragment showFields on Show { title releaseYear now } 输出 { "data": { "golang": [ { "title": "golang", "releaseYear": 2009, "now": "2021-03-08 13:46:43" } ], "java": [ { "title": "java", "releaseYear": 1995, "now": "2021-03-08 13:46:43" } ] } }
上面的on后面的类型必须在.graphql文件中有定义
多个.graphqls的使用
自定义address.graphqls
# 多个.graphqls定义需要使用这种方式 extend type Query { listAddress(id: Int): Address } extend type Mutation { addAddress(input: AddressInput): Int } type Address { id: Int street: String city: String country: String customerId: Int } input AddressInput { street: String city: String country: String customerId: Int }
自定义的其他的schema文件查询需要使用type extend 具体类型
方式
Mutation
mutation用于修改对象,dgs使用注意事项
@DgsData中的parentType="Mutation",field需要与schema中的保持一致
对象类型在java中定义的同时,需要在对应的.graphqls文件中定义,类型是input
input AddressInput { street: String city: String country: String customerId: Int }
graphiql查询页面中的对象要放在变量中定义
查询输入框的示例 添加以对象格式传参 mutation AddAddress($addAddress: AddressInput) { addAddress(input: $addAddress) } 修改的请求参数直接以参数形式传递 mutation UpdateAddress { updateAddress(id: 1, street: "凤阳街道", city: "广州") { id street city customerId } } query variables中的变量值 { "addAddress": { "street": "江海街道", "city": "广州", "country": "中国", "customerId": 1 } }
变更中的多个字段(Multiple fields in mutations) 一个变更也能包含多个字段,一如查询。查询和变更之间名称之外的一个重要区别是:
非变量定义方式
mutation AddAddress1 { addAddress(input: { street: "昌岗街道", city: "广州", country: "中国", customerId: 1 }) } ![](https://gitee.com/sharehappy/figurebed/raw/master/md/img/2021/04/20210421183524.png)
查询字段时,是并行执行,而变更字段时,是线性执行,一个接着一个。
操作名称
上面我们主要是查询,省略了query
关键字和查询名称,生产中加上这些可以减少代码歧义
query Abc { hello(name: "zhangsan") } mutation AddAddress($addAddress: AddressInput) { addAddress(input: $addAddress) } mutation UpdateAddress { updateAddress(id: 1, street: "凤阳街道", city: "广州") { id street city customerId } }
操作类型可以是 query、mutation 或 subscription,描述你打算做什么类型的操作。操作类型是必需的,除非你使用查询简写语法,在这种情况下,你无法为操作提供名称或变量定义。
操作名称是你的操作的有意义和明确的名称。它仅在有多个操作的文档中是必需的,但我们鼓励使用它,因为它对于调试和服务器端日志记录非常有用。 当在你的网络或是 GraphQL 服务器的日志中出现问题时,通过名称来从你的代码库中找到一个查询比尝试去破译内容更加容易。 就把它想成你喜欢的程序语言中的函数名
变量(Variables)
前面都是基于多个参数查询,为了扩展减少修改代码,我们通常将多个参数封装成对象。GraphQL 拥有一级方法将动态值提取到查询之外,然后作为分离的字典传进去。这些动态值即称为变量
使用变量之前,我们得做三件事:
使用 $variableName 替代查询中的静态值。
声明 $variableName 为查询接受的变量之一。
将 variableName: value 通过传输专用(通常是 JSON)的分离的变量字典中。
示例:
添加地址的变量 mutation AddAddress($addAddress: AddressInput) { addAddress(input: $addAddress) } 输入框中的实际变量值 { "addAddress": { "street": "江海街道", "city": "广州", "country": "中国", "customerId": 1 } }
变量定义(Variable definitions)
变量定义看上去像是上述查询中的 ($addAddress: AddressInput)。其工作方式跟类型语言中函数的参数定义一样。它以列出所有变量,<font color="red">变量前缀必须为 $</font>,后跟其类型,本例中为 AddressInput。
所有声明的变量都必须是标量、枚举型或者输入对象类型。所以如果想要传递一个复杂对象到一个字段上,你必须知道服务器上其匹配的类型。可以从Schema页面了解更多关于输入对象类型的信息。
变量定义可以是可选的或者必要的。上例中,AddressInput 后并没有 <font color="red">!</font>,因此其是可选的。但是如果你传递变量的字段要求非空参数,那变量一定是必要的。
默认变量(Default variables)
可以通过在查询中的类型定义后面附带默认值的方式,将默认值赋给变量。
query HeroNameAndFriends($episode: Episode = "JEDI") { hero(episode: $episode) { name friends { name } } }
当所有变量都有默认值的时候,你可以不传变量直接调用查询。如果任何变量作为变量字典的部分传递了,它将覆盖其默认值。
指令(Directives)
指令可以用来动态控制是否显示某个字段,有些场景下我们可能需要使用对象的A属性,一些场景不需要A属性,可以使用指令控制
query movies($withDirector: Boolean!) { movies { title director @skip(if: $withDirector) actor @include (if: $withDirector) { name } actor { home } } } 变量 { "withDirector": false }
变量值为true 变量值为false
对于字段操作可以使用
@skip
,如果对对象中的某些属性操作可以使用@include
我们用了 GraphQL 中一种称作指令的新特性。一个指令可以附着在字段或者片段包含的字段上,然后以任何服务端期待的方式来改变查询的执行。GraphQL 的核心规范包含两个指令,其必须被任何规范兼容的 GraphQL 服务器实现所支持:
@include(if: Boolean) 仅在参数为 true 时,包含此字段。
@skip(if: Boolean) 如果参数为 true,跳过此字段。 指令在你不得不通过字符串操作来增减查询的字段时解救你。服务端实现也可以定义新的指令来添加新的特性。
内联片段(inline fragment)
如果你查询的字段返回的是接口或者联合类型,那么你可能需要使用内联片段来取出下层具体类型的数据:
/** * 根据枚举类型不同获取不同类型的结果,graphql中使用的是内联片段 * @param filter * @return */ @DgsData(parentType = "Query", field = "movieFilter") public List<movie> movieFilter(@InputArgument("filter")MovieType filter) { return initMovie(filter); } private List<movie> initMovie(MovieType movieType) { List<movie> movies = new ArrayList<>(); Actor actor1 = new Actor(); actor1.setHome("广州"); actor1.setName("张三"); Actor actor2 = new Actor(); actor2.setHome("上海"); actor2.setName("李四"); if (movieType.equals(MovieType.Action) || movieType.equals(MovieType.All)) { movies.add(new ActionMovie("Crouching Tiger", null, 0, actor1)); movies.add(new ActionMovie("Black hawk down", null, 10, actor1)); } if (movieType.equals(MovieType.Scary) || movieType.equals(MovieType.All)) { movies.add(new ScaryMovie("American Horror Story", null, true, 10, actor2)); movies.add(new ScaryMovie("Love Death + Robots", null, false, 4, actor2)); } return movies; }
schema
# 定义枚举 enum MovieType { Scary Action All } type Query { movieFilter(filter: MovieType): [Movie] } graphiql查询
元字段(Meta fields)
某些情况下,你并不知道你将从 GraphQL 服务获得什么类型,这时候你就需要一些方法在客户端来决定如何处理这些数据。GraphQL 允许你在查询的任何位置请求 __typename,一个元字段,以获得那个位置的对象类型名称
schema和类型
枚举
枚举类型是一种特殊的标量,它限制在一个特殊的可选值集合内。
验证这个类型的任何参数是可选值的的某一个
与类型系统沟通,一个字段总是一个有限值集合的其中一个值。
enum Episode { NEWHOPE EMPIRE JEDI }
接口(Interfaces)
跟许多类型系统一样,GraphQL 支持接口。一个接口是一个抽象类型,它包含某些字段,而对象类型必须包含这些字段,才能算实现了这个接口。
interface Movie { title: String director: String actor: Actor } type ScaryMovie implements Movie { title: String director: String gory: Boolean scareFactor: Int actor: Actor } type ActionMovie implements Movie { title: String director: String nrOfExplosions: Int actor: Actor }
联合类型(union)
联合类型和接口十分相似,但是它并不指定类型之间的任何共同字段。
union MovieResult = ScaryMovie | ActionMovie
在我们的schema中,任何返回一个 MovieResult 类型的地方,都可能得到一个 ScaryMovie、ActionMovie p。注意,联合类型的成员需要是具体对象类型;你不能使用接口或者其他联合类型来创造一个联合类型。
这时候,如果你需要查询一个返回 SearchResult 联合类型的字段,那么你得使用条件片段才能查询任意字段。
schema # union 联合类型 movieFilter(filter: MovieType): [MovieResult] 查询 query Movie($filter: MovieType!) { movieFilter(filter: $filter) { __typename ... on ScaryMovie { scareFactor title director actor { name home } } ... on ActionMovie { nrOfExplosions title director actor { name home } } } }
联合类型可以理解为根据不同的参数返回不同类型数据的组合,与接口稍有不同,可以对比schema和查询结果
graph-java中的Instrumentation
实现instrumentation接口或者继承SimpleInstrumentation可以进行日志、权限处理,类似spring中的aop
@Configuration public class GraphQLConfig { @Bean @Primary public Instrumentation instrumentation() { Instrumentation instrumentation = new ChainedInstrumentation(new TracingInstrumentation(), new CustomInstrumentation()); return instrumentation; } }
加@Primary是由于DgsAutoConfiguration.kt中有对应的bean注入了
分页
graphql中的分页只能知道是否还有下一页数据,不能获取总量和当前页,这种结果可以结合dataLoader缓存根据条件查询滚动翻页 schema.graphqls
### 分页相关 type AddressConnection { edges: [AddressEdge] pageInfo: PageInfo } type AddressEdge { cursor: String node: Address } type PageInfo { hasPreviousPage: Boolean! hasNextPage: Boolean! }
/** * 分页查询 * @param environment * @param customerId 查询条件 * @param first 后面的的多少条结果 * @param after 每条数据都有游标,当前游标后的几条数据 与first配合使用,null则从第一条开始 * @param last 前面多少条数据 * @param before 当前游标前的几条数据 与last配合使用,null则从最后一条开始 * @return */ @DgsData(parentType = "Query", field = "pageAddress") public Connection<address> pageAddress(DataFetchingEnvironment environment, @InputArgument("customerId")Integer customerId, @InputArgument("first")Integer first, @InputArgument("after")String after, @InputArgument("last")Integer last, @InputArgument("before")String before) { List<address> addressList = addressMapper.selectList(Wrappers.<address>lambdaQuery().eq(Address::getCustomerId, customerId)); return new SimpleListConnection(addressList).get(environment); }
“如何使用GraphQL”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注网站,小编将为大家输出更多高质量的实用文章!