文章目录
- [1 GraphQL架构风格](#1 GraphQL架构风格)
-
- [1.1 简介](#1.1 简介)
- [1.2 GraphQL执行流程](#1.2 GraphQL执行流程)
- [1.3 相关语法](#1.3 相关语法)
-
- [1.3.1 Query(查询)](#1.3.1 Query(查询))
- [1.3.2 Mutation(变更)](#1.3.2 Mutation(变更))
- [1.3.3 Subscription(订阅)](#1.3.3 Subscription(订阅))
- [1.3.4 变量系统(Variable)](#1.3.4 变量系统(Variable))
- [1.3.5 指令(Directive)](#1.3.5 指令(Directive))
- [1.3.6 Introspection(自省)元字段](#1.3.6 Introspection(自省)元字段)
- [1.3.7 错误与响应格式](#1.3.7 错误与响应格式)
- [1.4 服务端操作](#1.4 服务端操作)
-
- [1.4.1 配置](#1.4.1 配置)
-
- [1.4.1.1 pom.xml](#1.4.1.1 pom.xml)
- [1.4.1.2 yml配置](#1.4.1.2 yml配置)
- [1.4.1.1 Schema](#1.4.1.1 Schema)
- [1.4.2 业务操作](#1.4.2 业务操作)
- [1.4.3 测试示例](#1.4.3 测试示例)
- [1.4.4 原生操作](#1.4.4 原生操作)
-
- [1.4.4.1 配置](#1.4.4.1 配置)
- [1.4.4.2 解析配置](#1.4.4.2 解析配置)
- [1.4.4.3 注册servlet](#1.4.4.3 注册servlet)
- [1.4.4.4 测试](#1.4.4.4 测试)
1 GraphQL架构风格
1.1 简介
有些小伙伴在工作中可能遇到过这样的场景:移动端只需要用户的姓名和邮箱,但REST API返回了用户的所有信息,造成数据传输浪费。
GraphQL正是为了解决这个问题而生的。
GraphQL包含三个核心组件:
Schema定义:强类型系统描述API能力查询语言:客户端精确请求需要的数据执行引擎:解析查询并返回结果
GraphQL的优缺点分析
- 优点:
精确的数据获取,避免过度获取
单一端点,减少HTTP连接开销
强类型系统,自动生成文档
前端主导数据需求 - 缺点:
查询复杂度控制困难
缓存实现复杂(HTTP缓存失效)
N+1查询问题需要额外处理
学习曲线相对陡峭
1.2 GraphQL执行流程

1.3 相关语法
GraphQL 操作语法其实只有 3 种顶层类型:Query、Mutation、Subscription,但写进纸面的"语法单元"有 10 来种。
可到此处进行验证语法:https://countries.trevorblades.com/
3 种根操作(Operation):
Query:只读Mutation:写操作Subscription:实时推送(基于 WebSocket)
语法模板,方括号代表可省
java
operationType [operationName] ( [variableDefinitions] ) {
selectionSet
}
1.3.1 Query(查询)
最简单字段列表
js
{
user(id: 4) {
name
avatar
}
}
起个操作名(方便调试/日志)
js
query GetUser {
user(id: 4) {
name
}
}
传变量(推荐,避免字符串拼接)
js
query GetUser($uid: ID!) { # 变量定义
user(id: $uid) {
name
posts { # 嵌套对象
title
comments(first: 3) { # 分页实参
body
}
}
}
}
此处ID表示是类型,加感叹号!区别:
| 写法 | 含义 | 举例 |
|---|---|---|
ID |
可以是 null 的 ID | "user-123" 或 null |
ID! |
绝对不能为 null 的 ID | 必须传 "user-123",传 null 或干脆不传都会报错 |
对于$使用变量声明时才用
| 位置 | 写法 | 角色 |
|---|---|---|
| 形参列表 | ($uid: ID!) |
变量声明------告诉 GraphQL"等会儿外部会传个变量叫 uid" |
| 查询体内 | user(id: $uid) |
变量使用------把刚才声明的那个变量插进来 |
别名(同一字段查两次)
js
{
smallPic: user(id: 4) { avatar(size: 64) }
bigPic: user(id: 4) { avatar(size: 512) }
}
Fragment(复用片段)
js
//必须指向 on User 表示 这套字段只能展开在 User 对象上
fragment AvatarInfo on User {
name
avatar
}
query {
u1: user(id: 4) { ...AvatarInfo }
u2: user(id: 5) { ...AvatarInfo }
}
内联片段(接口/联合类型),... on Type { fields } 叫 inline fragment(内联片段),按类型挑选字段
js
{
search(keyword: "hero") {
// GraphQL 内置字段,任何对象里都能拿,返回当前对象的真实类型名(字符串)
__typename
... on Movie { title }
... on Book { author }
}
}
1.3.2 Mutation(变更)
创建 + 返回结果
js
//mutation 声明"我要改" CreatePost 本次操作的命名
mutation CreatePost($input: PostInput!) {
// schema 里定义的 mutation 字段
createPost(input: $input) {
// 要返回什么字段
id
title
author { name }
}
}
多个写操作顺序执行(GraphQL 保证串行)
js
mutation {
addComment(postId: 1, body: "nice") { id }
updatePost(id: 1, views: +1) { views }
}
1.3.3 Subscription(订阅)
语法与 Query 相同,但由服务器推回
js
subscription MessageAdded($room: ID!) {
messageAdded(roomId: $room) {
from { name }
content
createdAt
}
}
客户端通过 WebSocket 发送该帧,服务器在有人发言时回相同结构数据。
1.3.4 变量系统(Variable)
声明:$var: Type = defaultValue,类型后加 ! 代表非空。
List / InputObject
js
query($ids: [ID!]!) {
users(ids: $ids) { name }
}
1.3.5 指令(Directive)
@skip(if: Boolean):如果为 true,就跳过这块,@include(if: Boolean):只有为 true 才保留这块,同时参数 if 必须是 Boolean!(非空布尔),变量、字面量都可以
js
query GetUser($skipAvatar: Boolean!) {
user(id: 4) {
name
avatar @skip(if: $skipAvatar)
}
}
1.3.6 Introspection(自省)元字段
Meta-field(元字段),带 __ 的字段不会出现在要写的 schema 里,但任何对象都能查询它们:
__typename:运行期真实类型,内置字段,任何对象里都能拿__schema:整个 schema 的"根目录",挂在最顶层的 Query隐式字段__type(name: "User"): 按名取类型详情,同样挂在顶层,用来查询某一个具体类型 的字段、枚举值、可能的接口实现等
js
{
__schema {
types {
name
kind
fields { name type { name } }
}
}
}
1.3.7 错误与响应格式
GraphQL 总是 HTTP 200,错误放在 errors 数组:
json
{
"data": { "user": null },
"errors": [{
"message": "User not found",
"path": ["user"],
"extensions": { "code": "USER_404" }
}]
}
1.4 服务端操作
1.4.1 配置
1.4.1.1 pom.xml
springboot 2.x 主要是 接口方式 比如 QueryResolver
xml
<dependency>
<groupId>com.graphql-java-kickstart</groupId>
<artifactId>graphql-spring-boot-starter</artifactId>
<version>11.1.0</version>
</dependency>
<!-- 测试客户端:GraphiQL(浏览器调试工具) -->
<dependency>
<groupId>com.graphql-java-kickstart</groupId>
<artifactId>graphiql-spring-boot-starter</artifactId>
<version>11.1.0</version>
</dependency>
spring 3.x 自带graphql 主要使用注解方式@SchemaMapping、@QueryMapping 注解方式而不是 QueryResolver
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-graphql</artifactId>
</dependency>
1.4.1.2 yml配置
yml
graphql:
servlet:
# graphql 访问地址
mapping: /graphql
auto-register: true
tools:
# graphql 一般存放resource 下的 graphql 文件夹以 .graphqls 结尾
schema-location-pattern: graphql/**/schema*.graphqls
# 浏览器内使用 GraphiQL 启用(默认 true)
graphiql:
enabled: true
mapping: /graphiql
1.4.1.1 Schema
js
type Query { //服务端定义而非客户端的 query 定义
userById(id: ID!): User
users: [User]
}
type User {
id: ID
userName: String
phoneNumer: String
remark: String
}
type Mutation {
addUser(userName: String!, phoneNumer: String!): Boolean # 添加
}
1.4.2 业务操作
java
//查询类
@Component
public class UserQuery implements GraphQLQueryResolver {
@Autowired
private UserService userService;
public List<UserEntity> users() {
return userService.list();
}
public UserEntity userById(String id) {
return userService.getById(id);
}
}
//新增类
@Component
public class UserMutation implements GraphQLMutationResolver {
@Autowired
private UserService userService;
public Boolean addUser(String userName, String phoneNumer) {
UserEntity book = new UserEntity();
book.setUserName(userName);
book.setPhoneNumer(phoneNumer);
return userService.save(book);
}
}
1.4.3 测试示例
查询graphql schema定义:
js
query userList{
users{
id
userName
phoneNumer
remark
}
}
结果:
{
"data": {
"users": [
{
"id": "1",
"userName": "哈哈",
"phoneNumer": "1234556789",
"remark": "1"
}
]
}
}
新增类示例:
js
mutation addUser{
addUser(userName: "小明", phoneNumer: "123456879465")
}
结果:
{
"data": {
"addUser": true
}
}
1.4.4 原生操作
1.4.4.1 配置
pom.xml
xml
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java</artifactId>
<version>24.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
schema如下
js
type Query {
userById(id: ID!): User
users: [User]
}
type User {
id: ID
userName: String
phoneNumer: String
remark: String
}
type Mutation {
addUser(userName: String!, phoneNumer: String!): Boolean # 添加
}
1.4.4.2 解析配置
java
@Configuration
public class GraphQLProvider {
private GraphQL graphQL;
@Autowired
private UserService userService; // 你的业务层
@Bean
public GraphQL graphQL() {
return graphQL;
}
@PostConstruct
public void init() throws IOException {
// 1. 自动加载 resources/graphql 目录下所有 .graphqls 文件
SchemaParser schemaParser = new SchemaParser();
TypeDefinitionRegistry typeRegistry = new TypeDefinitionRegistry();
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("classpath:graphql/**/*.graphqls");
for (Resource resource : resources) {
String sdl = StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8);
typeRegistry.merge(schemaParser.parse(sdl));
}
// 2. 绑定 DataFetcher(这就是替代 GraphQLQueryResolver 的方式)
RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
.type("Query", builder -> builder
.dataFetcher("users", env -> userService.list())
.dataFetcher("userById", env -> {
String id = env.getArgument("id");
return userService.getById(id);
})
)
.type("Mutation", builder -> builder
.dataFetcher("addUser", env -> {
String userName = env.getArgument("userName");
String phoneNumer = env.getArgument("phoneNumer");
UserEntity user = new UserEntity();
user.setUserName(userName);
user.setPhoneNumer(phoneNumer);
return userService.save(user);
})
)
.build();
// 3. 生成可执行 schema
SchemaGenerator schemaGenerator = new SchemaGenerator();
GraphQLSchema graphQLSchema = schemaGenerator.makeExecutableSchema(
SchemaGenerator.Options.defaultOptions(), // 可自定义严格校验等
typeRegistry,
runtimeWiring
);
// 4. 创建 GraphQL 实例
this.graphQL = GraphQL.newGraphQL(graphQLSchema).build();
}
}
1.4.4.3 注册servlet
java
@WebServlet(urlPatterns = "/graphql")
public class GraphQLServletConfig extends HttpServlet {
private final GraphQL graphQL; // 从 GraphQLProvider 注入,或静态持有
public GraphQLServletConfig(GraphQL graphQL) {
this.graphQL = graphQL;
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
// 1. 解析请求 body(GraphQL query JSON)
String requestBody = req.getReader().lines().collect(Collectors.joining());
Map<String, Object> input = new ObjectMapper().readValue(requestBody, Map.class);
String query = (String) input.get("query");
Map<String, Object> variables = (Map<String, Object>) input.getOrDefault("variables", Collections.emptyMap());
// 2. 执行 GraphQL
ExecutionInput executionInput = ExecutionInput.newExecutionInput()
.query(query)
.variables(variables)
.build();
ExecutionResult executionResult = graphQL.execute(executionInput);
// 3. 返回 JSON 响应
resp.setContentType("application/json");
resp.setCharacterEncoding("UTF-8");
resp.getWriter().write(new ObjectMapper().writeValueAsString(executionResult.toSpecification()));
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
// 支持 GET(可选,GraphQL 规范允许)
doPost(req, resp);
}
}
注意:启动类别忘了@ServletComponentScan
1.4.4.4 测试
用bruno 模拟测试
js
查询示例
query {
users {
id
userName
phoneNumer
remark
}
}
修改示例
mutation {
addUser(
userName:"小王"
phoneNumer:"987654321"
)
}