graphql-solon-plugin
<dependency>
<groupId>org.noear</groupId>
<artifactId>graphql-solon-plugin</artifactId>
</dependency>
1. 描述
数据扩展插件,为 Solon Data 提供基于 GraphQL-Java 的框架适配,主要提供一些注解方便使用。
注解
注解 | 用途 |
---|---|
@QueryMapping | 查询、修改 |
@BatchMapping | 批量查询 |
@SchemaMapping | 结构映射 |
@SubscriptionMapping | 订阅,实时获取数据更新 |
除了 @SubscriptionMapping
外的注解都有三个字段:value、field、typeName
-
value 和 field 等效:表示在 GraphQL 中绑定的字段名称,未指定指定 value 或 field 则默认为方法名
-
typeName: 表示 GraphQL 字段的 source/parent 类型的名称,未指定则由注入的参数类名派生
@SubscriptionMapping
注解三个字段名称为:value、name、typeName,含义类似。
HTTP接口
对外提供三个 http 接口:
请求路径 | 请求方式 | 作用 |
---|---|---|
/graphql | POST | 接收 GraphQL 查询,返回数据 |
/schema | POST | 获取所有 schema 结构,无需请求体 |
/graphiql | GET | GraphQL 交互式网页,常用于测试 GraphQL 语句编写 |
2. 配置示例
server.port: 8081
无特殊配置,只需指定 GraphQL 服务端口即可。
3. 应用示例
代码均来自插件测试用例,可参见 这里 获取完整示例。
在 classpath:graphql
目录下(如 resources/graphql
)添加 graphqls 或者 gqls 后缀的文件,用以定义 GraphQL 对象类型。
在 resources/graphql
中添加 base.gqls
用于定义基础操作对象
type Query {
}
type Mutation {
}
type Subscription {
}
然后在其他 gqls 文件中使用 extend
,在基础操作对象中添加操作表明该操作的类型,具体可参见下文。
@QueryMapping
首先需要定义查询对象
extend type Query {
bookById(id: ID): Book
}
type Book {
id: ID
name: String
pageCount: Int
author: Author
}
extend type Query
添加一个操作名称为 bookById
的操作到 Query
对象,表明该操作操作类型为 Query,而后根据参数 id 查询数据。
在 Java 代码中使用 @QueryMapping
注解:
@QueryMapping
public BookInputDTO bookById(@Param String id) {
return this.generateNewOne(id);
}
操作类型 typeName 默认为 Query(@QueryMapping
中默认),操作名称默认为方法名 bookById
。在GraphQL 定义的参数名称(bookById(id: ID): Book
中的 id
)和注解修饰方法的入参(@Param String id
中的id
)名称需要保持一致。
而后就可以向指定地址(如 http://localhost:8081/graphql
)发送 GraphQL 查询语句,在请求体中以 JSON 格式发送,使用 query
和 variables
字段分别指定 GraphQL 查询语句和参数,就可以发送查询请求了(还可以指定 operationName
)。请求和相应结果可参考 GraphQL官网。
可将 query
字段置为:
query ($id: ID){
bookById(id: $id){
id
name
pageCount
author {
firstName
lastName
}
}
}
将 variables
字段指定为:
{
"id": "book-1"
}
variables 中的字段名称 id
需要和 query 中的变量名称 $id
保持一致
@BatchMapping
批量查询分两种:一对一批量查询,如一个课程对应一个讲师;一对多批量查询,如一个课程对应多个学生。
一对一
定义查询对象
extend type Query {
courses: [Course]
}
type Course {
id: ID
name: String
instructor: Person
students: [Person]
}
type Person {
id: ID
firstName: String
lastName: String
}
使用 @BatchMapping
注解
@QueryMapping
public Collection<Course> courses() {
return CourseSupport.courseMap.values();
}
@BatchMapping
public List<Person> instructor(List<Course> courses) {
return courses.stream().map(Course::instructor).collect(Collectors.toList());
}
发送 GraphQL 查询请求
query {
courses {
id
name
instructor {
id
firstName
lastName
}
}
}
其中 courses
首先调用上述 @QueryMapping
修饰的 public Collection<Course> courses()
查询,查到多个课程。而后 instrucor
又调用 @BatchMapping
修饰的 public List<Person> instructor(List<Course> courses)
将每个课程的讲师信息填入。这就是 GraphQL 带来的好处之一,不用手动一个个调用接口然后自己组装数据。
一对多
查询对象在一对一中已定义,使用 @BatchMapping
注解
@BatchMapping
public List<List<Person>> students(List<Course> courses) {
return courses.stream().map(Course::students).collect(Collectors.toList());
}
发送 GraphQL 查询请求:
query {
courses {
id
name
students {
id
firstName
lastName
}
}
}
整体和一对一类似,不同之处在于 courses 查询中的 students 结果为数组,会获取多个学生信息。
@SchemaMapping
用于结构映射,指定 typeName 为 source/parent 的类型,指定 field 绑定 GraphQL 字段,当查询该字段时,会调用该注解修饰的方法,同时根据 typeName 获取到父类型数据。如:
@SchemaMapping(field = "author", typeName = "Book")
public AuthorInputDTO authorByBookId(BookInputDTO book) {
return this.getDefaultAuthor();
}
指明绑定 author
字段,父类型名称为 Book
当收到如下查询时:
query ($id: ID){
bookById(id: $id){
id
name
pageCount
author {
firstName
lastName
}
}
}
首先根据 bookById 查询数据,遇到 author
时调用上述 @SchemaMapping(field = "author", typeName = "Book")
修饰的方法,获取父类数据到 BookInputDTO book
而后根据 book 查询作者(逻辑上是这样,代码中没有根据 book 的数据获取作者)。
@SubscriptionMapping
定义 notifyProductPriceChange
订阅类型
extend type Subscription {
notifyProductPriceChange (productId: ID): ProductPriceHistory
}
type ProductPriceHistory {
id: ID!
price: Int!
startDate: String!
}
其次使用 @SubscriptionMapping
注解订阅
@SubscriptionMapping("notifyProductPriceChange")
public Flux<ProductPriceHistoryDTO> notifyProductPriceChange(@Param Long productId) {
// A flux is the publisher of data
return Flux.fromStream(
Stream.generate(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return new ProductPriceHistoryDTO(productId, new Date(),
(int) (rn.nextInt(10) + 1 + productId));
}));
}
返回 Flux 对象可用于订阅事件然后解析做相关处理。