我在Spring Boot中尝试了GraphQL

首先

我虽然稍晚了解到GraphQL这个东西,但我尝试使用我熟悉的Java+Spring Boot进行了实现。
以下将描述实现的相关内容,关于GraphQL本身的解释请参考这里或其他资源。

项目准备

开发环境

    • Windows 10

 

    • Java 11 (AdoptOpenJDK jdk11.0.4+11 OpenJ9 0.15.1)

 

    • Maven 3.5.4

 

    STS 4.4.0

创建一个基于Spring Boot的项目

    Spring Boot 2.1.9
依存関係
備考Spring Web必須サーブレットとして起動するためLombok推奨エンティティクラスを作成する上で便利なのでJDBC API選択DBアクセスの方法はお好みでH2 Database〃〃MyBatis Framework〃〃

添加GraphQL Java的依赖关系

使用 graphql-java-kickstart/graphql-spring-boot。只需以下最小限度的依赖即可执行。

<dependency>
    <groupId>com.graphql-java-kickstart</groupId>
    <artifactId>graphql-spring-boot-starter</artifactId>
    <version>5.10.0</version>
</dependency>

在Kotlin中必须指定版本作为属性(如果没有写明,将在运行时发生异常)。

<properties>
    <java.version>11</java.version>
    <kotlin.version>1.3.10</kotlin.version>
</properties>

API 1:支持简单的获取查询。

考虑根据ID从以下模型中获取一个实体。

成员(成员ID,姓名,年龄)

创建GraphQL定义

请在src/main/resources/graphql文件夹下创建一个包含类型定义和查询定义的文件。

type Member {
    memberId: ID!
    name: String!
    age: Int
}

type Query {
    getMember(memberId:ID!): Member
}

Java方面的实现

实体类

首先,根据模型定义创建合适的实体类。

@Data
public class Member {
    private String memberId;
    private String name;
    private Integer age;
}
查询

接下来要创建一个能够响应查询的方法。创建一个实现了GraphQLQueryResolver的类,并创建与查询名称相同的方法。定义参数和返回值的对应类,并实现返回实例的逻辑。返回值的创建方式可以任意选择(使用MyBatis的Mapper来实现,此处省略)。将可能作为Servlet返回值的所有字段都填充好。

@Component
public class MemberQueryResolver implements GraphQLQueryResolver {

    @Autowired
    MemberMapper mapper;

    public Member getMember(String memberId) {
        return mapper.getById(memberId);
    }
}

执行

启动配置

在application.yml中编写Servlet的启动配置。只需要有以下设置就可以启动。请参阅此处获取更多详细信息。

graphql:
  servlet:
    enabled: true
    mapping: /graphql
    corsEnabled: true
启动

写完描述后,将创建的项目作为Spring Boot应用程序运行。

查询请求

当向 http://localhost:8080/graphql 发送带有 GraphQL 查询的 URL 参数 query 的请求时,将获得相应的结果。

举个例子,如果要执行以下查询,

query {
  getMember(memberId:"aaaaaaaa") {
    name
  }
}

不含多余的空格和换行符的情况下,使用encodeURI进行编码,得到的结果为query%7BgetMember(memberId:%22aaaaaaaa%22)%7Bname%7D%7D,请使用curl命令进行验证。

$ curl -s "http://localhost:8080/graphql?query=query%7BgetMember(memberId:%22aaaaaaaa%22)%7Bname%7D%7D"
{"data":{"getMember":{"name":"齊藤京子"}}}

如果更改memberId并将age添加到获取键中,则会得到以下结果。

$ curl -s "http://localhost:8080/graphql?query=query%7BgetMember(memberId:%22bbbbbbbb%22)%7Bname,age%7D%7D"
{"data":{"getMember":{"name":"佐々木久美","age":23}}}

引入GraphiQL

由于每次对查询进行encodeURI并使用curl或浏览器访问太麻烦,所以引入一个用于测试查询的工具。这次我们将使用GraphiQL。

添加依存关系

在项目的依赖中添加图「i」的ql-spring-boot-starter。

<dependency>
    <groupId>com.graphql-java-kickstart</groupId>
    <artifactId>graphiql-spring-boot-starter</artifactId>
    <version>5.10.0</version>
</dependency>

执行

启动配置

将启动配置写入application.yml文件中的servlet。只需有以下设置即可启动。详细信息请参考此处。上述所述的GraphQL设置是不需要的。

graphiql:
  enabled: true
  mapping: /graphiql
  endpoint:
    graphql: /graphql
启动

将描述的项目创建为Spring Boot App,并运行。
服务器启动后,通过浏览器访问http://localhost:8080/graphiql。

GraphiQL.PNG

在左侧的查询创建界面上,提供了字段名称的输入辅助,并显示已经格式化的执行结果的JSON。

API第二部分:尝试嵌套类型

考虑对于那些通过关联将多对多模型连接起来的模型,除了在模型1中的模型之外,通过ID获取一个实体。

成员(成员ID,姓名,年龄)
歌曲(歌曲ID,曲名)
参与歌曲关系(歌曲ID,成员ID)

GraphQL定义修改

在定义文件中进行追加。由于是多对多关系,所以对于Member的Music以及Music的Member都会变成数组。

type Member {
    memberId: ID!
    name: String!
    age: Int
    joining: [Music]
}

type Music {
    musicId: ID!
    title: String!
    members: [Member]
}

type Query {
    getMember(memberId:ID!): Member
    getMusic(musicId:ID!):Music
}

Java方面的实现

实体类

创建一个能够适应新类型定义的实体类。将以数组形式定义的属性改为使用List。

@Data
public class Music {
    private String musicId;
    private String title;
    private List<Member> members;
}

由于Member类添加了属性,因此需要添加字段。

@Data
public class Member {
    private String memberId;
    private String name;
    private int age;
    private List<Music> joining;
}
查询

MemberQueryResolver保持不变。将插入值逻辑到另一个类中创建。在DataClass(<>中)中,创建一个指定插入目标实体类的GraphQLResolver实现类,并创建一个与插入目标字段同名的方法。参数中指定插入目标的实体类,并返回要插入的实例。
(MusicMapper.getMusicsOfMember是通过关联到memberId来获取所有与Music相关的方法(实现内容略)。)

@Component
public class MemberResolver implements GraphQLResolver<Member> {

    @Autowired
    MusicMapper mapper;

    public List<Music> joining(Member member) {
        return mapper.getMusicsOfMember(member.getMemberId());
    }
}

执行

当您发出以下查询时

query {
  getMember(memberId:"cccccccc") {
    name, age, joining {
      title
    }
  }
}

返回的数据如下。

{
  "data": {
    "getMember": {
      "name": "富田鈴花",
      "age": 18,
      "joining": [
        {
          "title": "こんなに好きになっちゃっていいの?"
        },
        {
          "title": "ホントの時間"
        },
        {
          "title": "まさか 偶然…"
        },
        {
          "title": "川は流れる"
        }
      ]
    }
  }
}

当在STS的调试模式中进行确认时,可以发现在MemberResolver.joining的参数中传递了一个实例,该实例中插入了除joining以外的其他字段的搜索结果。此外,如果在查询中未指定joining或指定了不存在的memberId并且结果为null,则不会执行此方法。

API第三部分:尝试创建注册功能

到目前为止,我们只处理了查询(相当于CRUD中的读取操作)。接下来,我们要尝试实现变更(相当于CRUD中的创建、更新和删除操作)。

GraphQL定義的增加

在定义文件中追加内容。

type Mutation {
    registerMusic(title: String!): Music
}

Java方面的实现

突变

由于实体是使用现有的东西,因此我们只需要创建与变异相对应的方法。创建一个实现GraphQLMutationResolver的类,并创建一个具有与查询相对应的参数和返回值的方法,以便与GraphQL兼容。generateId()是一个生成ID字符串的方法,MusicMapper.insert是一个插入所有参数信息的方法(实现细节略)。

@Component
public class MusicMutationResolver implements GraphQLMutationResolver {

    @Autowired
    MusicMapper mapper;

    public Music registerMusic(String title) {
        Music music = new Music();
        music.setMusicId(generateId());
        music.setTitle(title);
        mapper.insert(music);
        return music;
    }
}

发出查询

对于mutation情况,请通过POST调用API。将查询附加为JSON格式的参数。请务必对查询中的“进行转义。

$ curl -s "http://localhost:8080/graphql" -X POST -H "Content-Type: application/json" -d '{"query":"mutation{registerMusic(title:\"NO WAR in the future\"){musicId,title}}"}'
{"data":{"registerMusic":{"musicId":"hogehoge","title":"NO WAR in the future"}}}

GraphiQL图形界面

如果要在GraphiQL中尝试,您可以在左侧写入mutation{…},并在内部像查询一样写入API名称、参数和输出内容。

GraphiQL_mutation.PNG

请参考

    • GraphQL入門 – 使いたくなるGraphQL – Qiita

 

    • Spring Boot + GraphQLでAPIを作成してみよう! – Yahoo! JAPAN Tech Blog

 

    • GraphQL Java

 

    GitHub – graphql-java-kickstart/graphql-spring-boot