我在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。
在左侧的查询创建界面上,提供了字段名称的输入辅助,并显示已经格式化的执行结果的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名称、参数和输出内容。
请参考
-
- GraphQL入門 – 使いたくなるGraphQL – Qiita
-
- Spring Boot + GraphQLでAPIを作成してみよう! – Yahoo! JAPAN Tech Blog
-
- GraphQL Java
- GitHub – graphql-java-kickstart/graphql-spring-boot