要实现的 API 的数据模型
将存储在数据库中的以下表重新定义为 GraphQL 模式,以实现能够获取灵活数据的 API。首先,以下图的数据模型为基础,开始进行 GraphQL 模式的定义。
各表的用途如下。
由于这只是个简单的例子,所以并没有完全规范化,请不要太在意。
- Account 表:管理用户的账户信息。
- Service_Group 表:管理用户所属的服务组,而服务组可以拥有多个团队。
- Team 表:管理用户所属的团队。
创建空白项目
首先,使用 Spring Initializr 创建一个空白项目并创建所需的包和目录。
所使用的构建工具、JDK、Spring Boot 及依赖库如下:
框架/库等 | 版本 |
---|---|
OpenJDK | 11 |
SpringBoot | 2.7.1 |
Maven | 3.5.4 |
Spring Web | - |
Spring for GraphQL | - |
Lombok | - |
H2 Database | - |
log4j2 | - |
此外,由于 Log4j2 不能在 Spring Initializr 中指定,因此需要直接将依赖关系添加到 pom.xml 中。
在这之前,需要通过 <exclusion> 来排除由 spring-boot-starter 依赖的日志库。
代码语言:xml复制<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>
示例应用程序结构
项目结构如下:
代码语言:bat复制spring-graphql-sample
├ src
│ ├ main
│ │ ├ java/com.spring.graphql.example
│ │ │ ├ controller
│ │ │ ├ entity
│ │ │ ├ repository
│ │ │ └ Main.java
│ │ │
│ │ └ resources
│ │ ├ graphql
│ │ │ └ graphql-schema.graphqls --- GraphQL 文件
│ │ ├ aplication.yaml
│ │ ├ log4j2.xml --- 日志输出配置
│ │ ├ schema.sql --- H2 数据库表初始化脚本
│ │ └ data.sql --- H2 数据库数据初始化脚本
│ │
│ └ test --- 用于测试(本例未使用)
└ pom.xml
实现准备
在直接实现 GraphQL API 之前,将对项目进行配置,包括将数据流入数据库和设置日志。
准备工作1:表定义和数据库连接定义设置
因为单独设置每个 DBMS 太麻烦了,所以将使用 H2DB。
为了在应用启动时将表和数据自动导入到 H2DB,将以下内容保存在 schema.sql 中。
(由于省略了 data.sql 的内容,请根据需要自行设置适当的数据)
代码语言: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 文件中进行数据库连接的定义设置。
代码语言: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复制<?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>
GraphQL API 实现
前期准备工作已完成,现在将开始实现主题的 GraphQL API。
步骤1:定义 GraphQL 模式
在 resources/graphql/ 中创建一个名为 graphql-schema.graphqls 的文件,并定义 GraphQL 模式和查询。
在这个例子中,将定义一个名为 accountById 的查询。
该查询允许通过将账户ID作为参数发送到API请求,获取与账户ID关联的账户信息,以及该账户所属的服务组信息和团队信息。
虽然没有太多实际意义,但还可以从获取的团队信息中获取团队所属的服务组信息。
GraphQL 模式的定义与数据库表定义不同,推荐以数据使用者易于理解的形式定义模式。
关于模式定义的规则和思考方式,请参考这个链接。
代码语言: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 类
创建用于存储从数据库中检索的数据的 DTO 类,并将其放置在 entity 包中。
由于定义了三个表,因此将创建每个表的DTO类。
此外,为了避免繁琐地编写 Setter/Getter,使用了 Lombok。
对于主键和关联变量,使用 @Id 注解。
代码语言:java复制Account.java
@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;
}
代码语言:java复制ServiceGroup.java
@Setter
@Getter
public class ServiceGroup {
@Id
private String serviceGroupId;
private String serviceGroupName;
}
代码语言:java复制@Setter
@Getter
public class Team {
@Id
private String teamId;
private String belongingServiceGroupId;
private String teamName;
private String teamAuthority;
}
步骤3:实现 Repository 接口
将使用 Spring Data JDBC 与数据库进行交互。
实现与每个表对应的 Repository 接口,并将其存储在 repository 包中。
(由于这次是简单的表结构,按表分别实现接口。)
通过 extends 指定的 CrudRepository 已经默认提供了 findById 方法,因此在以主键作为参数检索数据时,不需要单独实现数据库查询。
代码语言:java复制public interface AccountRepository extends CrudRepository<Account, String>{
}
代码语言:java复制public interface ServiceGroupRepository extends CrudRepository<ServiceGroup, String> {
}
对于 TeamRepository,由于需要实现除主键之外的参数的 SELECT 查询,因此需要按照以下示例定义方法。
代码语言:java复制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:实现 Controller 类
代码语言:java复制@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();
}
}
步骤5:配置 GraphQL 端点 URL
在 application.yaml 文件中进行 GraphQL API 端点路径的配置和启用。
代码语言:yml复制spring:
# 省略了数据源定义
graphql:
graphiql:
enabled: true
path: /graphiqls
至此,API 的实现已经完成。
接下来,将启动 API 应用程序并进行操作确认。
启动 GraphQL API 服务器并进行操作确认
要启动 API 应用程序,只需运行 Main.java。
可以使用 Maven 进行构建并运行 JAR 文件的方法,也可以使用 IDE 功能进行运行,具体方法随意选择。
此外,将使用 GraphiQL 作为 GraphQL 的客户端工具。
- 端点:http://localhost:8080/graphql
- 方法:POST
操作确认1
通过执行以下查询,成功获取了帐户信息。
代码语言:GraphQL复制query {
accountById(accountId:"ACC01") {
accountId,
userName,
age,
accountType
}
操作确认2
接下来,将获取与帐户信息相关的各种信息。
通过执行以下查询,成功一次性获取了帐户信息及其关联的服务组、团队和团队关联的服务组信息。
代码语言:GraphQL复制query {
accountById(accountId:"ACC01") {
accountId,
userName,
age,
accountType,
serviceGroup {
serviceGroupId
serviceGroupName
}
team {
teamId
teamName
teamAuthority
serviceGroup {
serviceGroupId
serviceGroupName
}
}
}
}
最后
这次介绍了简单查询示例的实现步骤,下一步可以尝试一些更高级的内容,如Mutation和分页。