Solon

graphql-solon-plugin

v2.7.5 </> markdown
<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 接口:

请求路径请求方式作用
/graphqlPOST接收 GraphQL 查询,返回数据
/schemaPOST获取所有 schema 结构,无需请求体
/graphiqlGETGraphQL 交互式网页,常用于测试 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 格式发送,使用 queryvariables 字段分别指定 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 对象可用于订阅事件然后解析做相关处理。