使用Spring for GraphQL构建GraphQL API的步骤
Spring for GraphQL是一个用于构建GraphQL API的Spring框架。
一般来说,构建Java的GraphQL API通常使用GraphQL Java。
如果使用SpringBoot,也是类似的情况,但是在2022年5月,Spring for Java发布了1.0.0版,并官方提供了对GraphQL的支持。
因为发布时间还不久,所以截至2022年7月,Spring for GraphQL的官方文档和示例内容还不是很丰富。
这次我们将参考样例和文档,独自地使用Spring for GraphQL来实现API的实施。
这次要实施的API数据模型
将存储在数据库中的以下表重新定义为GraphQL模式,并实现能够获取灵活数据的API。
首先,我们将根据下图的数据模型进行GraphQL模式定义。

以下是各个表的使用目的。
另外,由于是随意创建的,可能没有完全进行规范化,请不要太在意这一点。
-
- Accountテーブル:ユーザのアカウント情報を管理します。
-
- Service_Groupテーブル:ユーザが所属するサービスグループを管理し、サービスグループは複数のチームを持ちます。
- Teamテーブル:ユーザが所属するチームを管理します。
创建空白项目
首先,在Spring Initializr上创建一个空白项目,并创建所需的包和目录。
使用的构建工具、JDK、SpringBoot和依赖库如下所示。
在Spring Initializr中无法指定Log4j2,因此需要直接在pom.xml中添加依赖项。为了排除spring-boot-starter依赖的日志库,需要使用进行排除。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
样本应用程序的构建
项目结构如下所示。
spring-graphql-sample
├ src
│ ├ main
│ │ ├ java/com.spring.graphql.example
│ │ │ ├ controller
│ │ │ ├ entity
│ │ │ ├ repository
│ │ │ └ Main.java
│ │ │
│ │ └ respurces
│ │ ├ graphql
│ │ │ └ graphql-schema.graphqls --- graphqlsファイル
│ │ ├ aplication.yaml
│ │ ├ log4j2.xml --- ログ出力設定
│ │ ├ schema.sql --- H2 DBテーブル初期化スクリプト
│ │ └ data.sql --- H2 DBデータ初期化スクリプト
│ │
│ └ test --- テスト用(今回使用せず)
└ pom.xml
准备实施
在开始实现GraphQL API之前,我们需要在项目中配置将数据和日志发送到数据库。
准备任务1: 表定义和数据库连接定义设置
因为分别设置DBMS会很麻烦,所以我们使用H2DB。
为了在应用启动时自动将表和数据流入H2DB,我们将以下内容保存到schema.sql中。
(省略data.sql的内容,请自行设置适当的数据。)
-- Service Group
CREATE TABLE service_group (
service_group_id VARCHAR(10) PRIMARY KEY COMMENT 'サービスグループID',
service_group_name VARCHAR(40) NOT NULL COMMENT 'サービスグループ名'
);
-- Team
CREATE TABLE team (
team_id VARCHAR(10) PRIMARY KEY COMMENT 'チームID',
belonging_service_group_id VARCHAR(10) NOT NULL COMMENT '所属サービスグループグID',
team_name VARCHAR(40) NOT NULL COMMENT 'チーム名',
team_authority VARCHAR(15) COMMENT 'チーム権限',
FOREIGN KEY (belonging_service_group_id) REFERENCES service_group (service_group_id)
);
-- Account Table
CREATE TABLE account (
account_id VARCHAR(10) PRIMARY KEY COMMENT 'アカウントID',
user_name VARCHAR(40) NOT NULL COMMENT 'ユーザ名',
age Int NOT NULL COMMENT '年齢',
account_type VARCHAR(10) NOT NULL COMMENT 'アカウント分類',
belonging_service_group_id VARCHAR(10) NOT NULL COMMENT '所属サービスグループID',
belonging_team_id VARCHAR(10) NOT NULL COMMENT '所属チームID',
FOREIGN KEY (belonging_service_group_id) REFERENCES service_group (service_group_id),
FOREIGN KEY (belonging_team_id) REFERENCES team (team_id)
);
接下来,可以在application.yaml文件中配置数据库连接定义。
spring:
datasource:
platform: h2
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=1;DB_CLOSE_ON_EXIT=FALSE;MODE=DB2
username: sa
password: ''
备忘录2:日志配置
我们将使用log4j2进行日志记录。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="error">
<Properties>
<Property name="format1">[%d{yyyy/MM/dd HH:mm:ss.SSS}] [%t] [%-6p] [%c{10}] : %m%n</Property>
</Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout>
<pattern>${format1}</pattern>
</PatternLayout>
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console" />
</Root>
</Loggers>
</Configuration>
GraphQLAPI的实现
由于事前准备工作已经完成,我们将开始实现主题的GraphQL API。
第一步:GraphQL的Schema定义
在resources/graphql/目录下创建一个名为graphql-schema.graphqls的文件,并定义GraphQL的模式和查询。
这次我们定义一个名为accountById的查询。
通过将账户ID作为参数发送API请求,可以获取与该账户ID相关联的账户信息、所属的服务组信息和团队信息。
此外,虽然意义不大,但也可以从获取的团队信息中获取所属的服务组信息。
在GraphQL中,推荐使用一种更易于数据使用者使用的形式来定义模式,与数据库的表定义不同。关于模式定义的规则和思考方式,请参考此链接。
# クエリ定義
type Query {
accountById(accountId: ID): Account
}
# タイプ定義
type ServiceGroup {
serviceGroupId: ID!
serviceGroupName: String
teams: [Team]
}
type Team {
teamId: ID!
teamName: String
teamAuthority: String
serviceGroup: ServiceGroup!
}
type Account {
accountId: ID!
userName: String
age: Int
accountType: String
serviceGroup: ServiceGroup!
team: Team!
}
# Enum定義
enum TeamAuthority {
PRIVILEGE
DEVELOP
AUDIT
}
enum AccountType {
ADMIN
DEVELOPER
GUEST
}
步骤2:实现DTO类
将从数据库中获取的数据存储到entity包中创建的DTO类中。
由于本次定义了三个表,因此需要为每个表创建相应的DTO类。
此外,为了避免繁琐地编写Setter/Getter方法,使用了Lombok。
对于与主键相关的变量,添加了@Id注解。
@Setter
@Getter
@AllArgsConstructor
public class Account {
@Id
private String accountId;
private String userName;
private int age;
private String accountType;
private String belongingServiceGroupId;
private String belongingTeamId;
}
@Setter
@Getter
public class ServiceGroup {
@Id
private String serviceGroupId;
private String serviceGroupName;
}
@Setter
@Getter
public class Team {
@Id
private String teamId;
private String belongingServiceGroupId;
private String teamName;
private String teamAuthority;
}
步骤3:实现RepositoryIF
在与数据库的交互中,我们使用了Spring Data JDBC。我们会实现与每个表对应的RepositoryIF,并将其存放在repository包中。(由于这次是简单的表结构,所以我们将IF按照表的单位进行了分离。)
由于CrudRepository中已经默认提供了findById方法,所以如果要通过主键参数获取数据,无需单独实现数据库查询。
public interface AccountRepository extends CrudRepository<Account, String>{
}
public interface ServiceGroupRepository extends CrudRepository<ServiceGroup, String> {
}
需要实现一个关于 TeamRepository 的方法,该方法的参数除了 PK 之外还有其他的 SELECT 查询,方法定义如下所示。
public interface TeamRepository extends CrudRepository<Team, String> {
@Query("SELECT team_id, belonging_service_group_id, team_name, team_authority" +
" FROM team WHERE belonging_service_group_id = :serviceGroupId")
List<Team> findByServiceGroupId(String serviceGroupId);
}
步骤4:实现控制器类
实现一个能够接收API请求的控制器类。
在带有@QueryMapping注释的方法中定义处理接收到的GraphQL查询的方法。
本次操作中,accountById是目标GraphQL查询,并通过在accountId上使用@Argument注释来明确接收参数的过程。
此外,我们将实现@SchemaMapping,以获取与AccountGraphQL模式相关联的serviceGroup和Team的处理。为了获取与Account相关联的ServiceGroup、Team以及与Team相关联的ServiceGroup,我们定义了三个方法。
@Controller
public class AccountGraphqlController {
private final Logger logger = LogManager.getLogger(AccountGraphqlController.class);
private AccountRepository accountRepository;
private ServiceGroupRepository serviceGroupRepository;
private TeamRepository teamRepository;
public AccountGraphqlController(final AccountRepository accountRepository,
final ServiceGroupRepository serviceGroupRepository,
final TeamRepository teamRepository) {
this.accountRepository = accountRepository;
this.serviceGroupRepository = serviceGroupRepository;
this.teamRepository = teamRepository;
}
@QueryMapping
public Account accountById(@Argument final String accountId) {
logger.info("=== Query Call, queryByAccountId. === ");
final Optional<Account> acc = accountRepository.findById(accountId);
return acc.get();
}
@SchemaMapping
public ServiceGroup serviceGroup(final Account account) {
final Optional<ServiceGroup> sg = serviceGroupRepository.findById(account.getBelongingServiceGroupId());
return sg.get();
}
@SchemaMapping
public Team team(final Account account) {
final Optional<Team> t = teamRepository.findById(account.getBelongingTeamId());
return t.get();
}
@SchemaMapping
public ServiceGroup serviceGroup(final Team team) {
final Optional<ServiceGroup> sg = serviceGroupRepository.findById(team.getBelongingServiceGroupId());
return sg.get();
}
}
步骤五:设置GraphQL端点URL。
在application.yaml文件中进行GraphQL API端点路径的配置和启用。
spring:
# データソース定義は省略
graphql:
graphiql:
enabled: true
path: /graphiqls
以上是API的实现完成。
接下来我们将启动实际的API应用程序并进行操作确认。
启动和测试GraphQLAPI服务器。
只需执行Main.java即可启动API应用程序。可以使用Maven构建并执行jar文件,也可以使用IDE的功能进行执行,无论使用哪种方法都可以。
我决定将GraphiQL用作为GraphQL的客户端工具。
-
- エンドポイント:http://localhost:8080/graphql
メソッド:POST
确认动作1
通过执行以下查询,我们成功获取了账户信息。
query {
accountById(accountId:"ACC01") {
accountId,
userName,
age,
accountType
}

确认动作2
通过执行以下查询,可以一次性获取帐户信息及其关联的服务组、团队和与团队关联的服务组信息。
query {
accountById(accountId:"ACC01") {
accountId,
userName,
age,
accountType,
serviceGroup {
serviceGroupId
serviceGroupName
}
team {
teamId
teamName
teamAuthority
serviceGroup {
serviceGroupId
serviceGroupName
}
}
}
}

最终
我們在這次講解中介紹了簡單的查詢示例實施步驟,但下次我們想試試Mutation和分頁等應用。