前言

GraphQL基本介绍可见文章:https://blog.liyi.xyz/posts/c6f3.html

1 配置

1.1 依赖导入

导入graphql、mysql、Mybatisplus相关依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

<!-- https://mvnrepository.com/artifact/com.graphql-java-kickstart/graphql-spring-boot-starter -->
<dependency>
<groupId>com.graphql-java-kickstart</groupId>
<artifactId>graphql-spring-boot-starter</artifactId>
<version>14.1.0</version>
</dependency>

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.32</version>
<scope>runtime</scope>
</dependency>

<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>

1.2 graphql的schema创建

在resources/graphql创建root.graphqls文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type Query {
# 根据书籍ID查询书籍信息
bookById(id: ID!): Book
}

type Book{
id: ID
name: String
pageCount: Int
author: Author
}

type Author{
id: ID
firstName: String
lastName: String
sex: Int
}

这个Schema定义了一个顶级字段(在Query类型中):bookById,它返回指定书籍的详情。

它也定义了类型Book,包含字段:id、name、pageCount、author,author的类型是Author。

上面用来描述Schema的专用语言叫做Schema Definition Language 或者叫DSL。更多细节可以查看:https://graphql.org/learn/schema/

image-20230508114048809

1.3 项目yaml配置

配置文件:application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
# 这里选择的数据库为test,需要在启动项目之前手动创建
url: jdbc:mysql://localhost:3306/test?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
# mysql连接账号密码
username: root
password: xxxx
sql:
# SpringBoot项目启动自动执行sql脚本配置
init:
mode: always
schema-locations: classpath:db/schema-v1.0.sql
data-locations: classpath:db/data-v1.0.sql
server:
port: 9090
graphql:
servlet:
mapping: /graphql
enabled: true
corsEnabled: true
tools:
schema-location-pattern: graphql/*.graphqls
graphiql:
# 开启graphiql可交互页面,用于调试
enabled: true
playground:
# 开启playground可交互页面,用于调试
enabled: true
logging:
level:
com.liyitongxue.graphql.dao: DEBUG

1.4 数据库

t_book、t_author两张表的数据如下

image-20230508114048810

2 项目代码

项目结构结构如下图所示:

image-20230508104322198

2.1 config

MybatisPlusConfig.java:

1
2
3
4
@Configuration
@MapperScan("com.liyitongxue.graphql.dao")
public class MybatisPlusConfig {
}

2.2 model

书籍实体类Book.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@TableName("t_book")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Book {
/**
* 主键
*/
@TableId(type = IdType.AUTO)
private String id;

/**
* 书名
*/
@TableField("name")
private String name;

/**
* 总页数
*/
@TableField("pageCount")
private int pageCount;

/**
* 作者Id
*/
@TableField("authorId")
private String authorId;
}

作者实体类Author.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@TableName("t_author")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Author {
/**
* 主键
*/
@TableId(type = IdType.AUTO)
private String id;

/**
* 名
*/
@TableField("firstName")
private String firstName;

/**
* 姓
*/
@TableField("lastName")
private String lastName;

/**
* 性别 0:女,1:男
*/
@TableField("sex")
private int sex;
}

2.3 dto

创建对应graphql中声明的java实体类BookDto.java:

1
2
3
4
5
6
7
@Data
public class BookDto {
String id;
String name;
int pageCount;
Author author;
}

2.4 mapper

BookMapper.java:

1
2
3
@Mapper
public interface BookMapper extends BaseMapper<Book> {
}

AuthorMapper.java:

1
2
3
@Mapper
public interface AuthorMapper extends BaseMapper<Author> {
}

2.5 service

IBookService.java:

1
2
3
public interface IBookService{
BookDto bookById(String id);
}

BookServiceImpl.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Service
public class BookServiceImpl implements IBookService {
@Resource
private BookMapper bookMapper;

@Resource
private AuthorMapper authorMapper;

@Override
public BookDto bookById(String id) {
BookDto bookDto = new BookDto();
Book book = bookMapper.selectById(id);
bookDto.setId(book.getId());
bookDto.setName(book.getName());
bookDto.setPageCount(book.getPageCount());

Author author = authorMapper.selectById(book.getAuthorId());
bookDto.setAuthor(author);

return bookDto;
}
}

2.6 datafetcher

BookDataFetcher.java:

1
2
3
4
5
6
7
8
9
@Component
public class BookDataFetcher {
@Resource
private IBookService bookService;

public Object bookById(DataFetchingEnvironment environment) {
return bookService.bookById(environment.getArgument("id"));
}
}

2.7 provider

GraphQLProvider.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Component
public class GraphQLProvider {
private final BookDataFetcher bookDataFetcher;

@Autowired
public GraphQLProvider(BookDataFetcher bookDataFetcher) {
this.bookDataFetcher = bookDataFetcher;
}

@Bean
public GraphQL graphQL() throws IOException {
TypeDefinitionRegistry typeRegistry = new TypeDefinitionRegistry();
SchemaParser schemaParser = new SchemaParser();
String[] schemaArr = {"root"};
for (String str : schemaArr) {
typeRegistry.merge(schemaParser.parse(new ClassPathResource("graphql/" + str + ".graphqls").getInputStream()));
}

RuntimeWiring runtimeWiring = buildWiring(); // 数据方法对应编写
SchemaGenerator schemaGenerator = new SchemaGenerator(); // 查询器构建
// 查询生成
GraphQLSchema graphQLSchema = schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);
return GraphQL.newGraphQL(graphQLSchema).build();
}

/**
* 业务实现,demo版
* 如果是开发实战,这一点的设计是重点,需要考虑如何根据graphql中定义的方法来执行java代码
*
* @return
*/
private RuntimeWiring buildWiring() {
return RuntimeWiring.newRuntimeWiring()
.type("Query", builder -> builder
.dataFetcher("bookById", environment -> bookDataFetcher.bookById(environment))
)
.build();
}
}

2.8 controller

BookController.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
@CrossOrigin
@RestController
@Slf4j
@RequestMapping("/graphql")
public class BookController {
private final GraphQL graphql;
private final ObjectMapper objectMapper;

@Autowired
public BookController(GraphQL graphql, ObjectMapper objectMapper) {
this.graphql = graphql;
this.objectMapper = objectMapper;
}

/**
* GET方式查询
*
* @param query 查询语句类json字符串
* @param operationName 查询操作名称-默认空字符
* @param variablesJson 查询参数变量-json字符串,默认为{}字符
* @return map对象
* @throws IOException json字符串转map对象异常
*/
@GetMapping
public Map<String, Object> graphqlGET(@RequestParam("query") String query,
@RequestParam(value = "operationName", defaultValue = "") String operationName,
@RequestParam(value = "variables", defaultValue = "{}") String variablesJson
) throws IOException {
Map<String, Object> variables = new LinkedHashMap<>();
if (variablesJson != null) {
variables = objectMapper.readValue(variablesJson, new TypeReference<Map<String, Object>>() {
});
}

return executeGraphqlQuery(query, operationName, variables);
}

/**
* POST方式查询
*
* @param body json对象方式
* @return map对象
*/
@PostMapping
public Map<String, Object> graphqlPOST(@RequestBody Map<String, Object> body) {
String query = (String) body.get("query");
if (query == null) {
query = "";
}
String operationName = (String) body.get("operationName");
Map<String, Object> variables = (Map<String, Object>) body.get("variables");
if (variables == null) {
variables = new LinkedHashMap<>();
}
return executeGraphqlQuery(query, operationName, variables);
}

/**
* 执行graphQL查询
*
* @param query 查询语句-类json字符
* @param operationName 查询操作名称-默认空字符
* @param variables 查询参数变量-map对象、默认为空map
* @return map对象
*/
private Map<String, Object> executeGraphqlQuery(String query, String operationName, Map<String, Object> variables) {
ExecutionInput executionInput = ExecutionInput.newExecutionInput()
.query(query)
.operationName(operationName)
.variables(variables)
.build();
return graphql.execute(executionInput).toSpecification();
}
}

3 浏览器可视化界面测试

启动SpringBoot后,进行图形化界面测试

浏览器访问:http://localhost:9090/playground,就会进入playground图形界面

浏览器访问:http://localhost:9090/graphiql,就会进入graphiql图形界面

输入以下GraphQL查询语句进行查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Write your query or mutation here
query{
bookById(id: "book-1"){
id
name
pageCount
author{
id
firstName
lastName
sex
}
}
}

查询结果如下:

可以看见查询结果中显示的字段与左侧需要查询的字段一致,如果需要删减字段,可在左侧直接进行删减

image-20230508105733773

DOCS可以查看定义的查询

image-20230508110112064

SCHEMA可以查看定义的对象类型详细信息

image-20230508110145844

4 接口测试工具请求测试

此处为Apipost进行测试,Postman同理

4.1 GET请求

image-20230508111045575

4.2 POST请求

image-20230508111433971

参考链接

https://blog.csdn.net/LookOutThe/article/details/121673944

https://juejin.cn/post/6844904051860045831