GraphQL系列 - 第2讲 Spring集成GraphQL

目录

    • 一、maven依赖
    • 二、Schema 定义
    • 三、代码集成
      • 3.1 创建模型类
      • 3.2 创建服务类
      • 3.3 创建控制器类
    • 四、单元测试
    • 五、实际 HTTP 请求测试
      • 5.1 查询单个 Person
      • 5.2 查询所有 People
      • 5.3 添加 Person
    • 六、其他
      • 6.1 开启graphiql
      • 6.2 开启schema查看端点

一、maven依赖

首先,在 pom.xml 文件中添加以下依赖:

<!-- Spring GraphQL -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-graphql</artifactId>
</dependency><!-- Spring GraphQL Test -->
<dependency><groupId>org.springframework.graphql</groupId><artifactId>spring-graphql-test</artifactId><scope>test</scope>
</dependency>
<!-- Unless already present in the compile scope -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId><scope>test</scope>
</dependency>

二、Schema 定义

src/main/resources/graphql 目录下创建 person.graphqls 文件,定义 GraphQL Schema:

type Query {person(id: ID!): Personpeople: [Person]
}type Mutation {addPerson(input: PersonInput!): Person
}type Person {id: ID!name: String!age: Intdept: Dept
}type Dept {id: ID!code: String!name: String!
}input PersonInput {name: String!age: IntdeptId: String
}

三、代码集成

3.1 创建模型类

PersonDeptPersonInput 模型类:

import lombok.Data;@Data
public class Person {private String id;private String name;private int age;private Dept dept;
}@Data
public class Dept {private String id;private String code;private String name;
}@Data
public class PersonInput {private String name;private int age;private String deptId;
}

3.2 创建服务类

PersonService 服务类:

注:如下服务类可根据需求调整,本实现仅作为示例作用,实际开发时可通过数据库存储、调用其他服务等来生成相应的底层数据。

import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;@Service
public class PersonService {private List<Person> people = new ArrayList<>();public Person getPersonById(String id) {return people.stream().filter(person -> person.getId().equals(id)).findFirst().orElse(null);}public Dept getDeptByPersonId(String id) {Dept dept = new Dept();dept.setId("dept_001");dept.setCode("001");dept.setName("dept001");return dept;}public List<Person> getAllPeople() {return people;}public Person addPerson(PersonInput input) {Person person = new Person();person.setId(UUID.randomUUID().toString());person.setName(input.getName());person.setAge(input.getAge());people.add(person);return person;}
}

3.3 创建控制器类

PersonController 控制器类:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.MutationMapping;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.data.method.annotation.SchemaMapping;
import org.springframework.stereotype.Controller;import java.util.List;@Controller
public class PersonController {@Autowiredprivate PersonService personService;@QueryMappingpublic Person person(@Argument String id) {return personService.getPersonById(id);}@SchemaMappingpublic Dept dept(Person person) {return personService.getDeptByPersonId(person.getId());}@QueryMappingpublic List<Person> people() {return personService.getAllPeople();}@MutationMappingpublic Person addPerson(@Argument PersonInput input) {return personService.addPerson(input);}
}

关于映射注解的说明可参见下表:

注解用途作用
@QueryMapping映射 GraphQL 查询操作将 GraphQL 查询请求映射到控制器中的方法
@MutationMapping映射 GraphQL 变更操作将 GraphQL 变更请求(如创建、更新、删除操作)映射到控制器中的方法
@SchemaMapping映射 GraphQL 模式中的字段将 GraphQL 模式中的字段映射到控制器中的方法,通常用于嵌套对象的解析

重点解释下@SchemaMapping注解,该注解将 GraphQL 模式中的字段映射到控制器中的方法,通常用于嵌套对象的解析,包括属性如下:

  • typeName:指定 GraphQL 类型的名称。默认情况下,Spring 会根据 方法参数类型 自动推断。
  • field:指定 GraphQL 字段的名称。默认情况下,Spring 会根据 方法名称 自动推断。

示例代码

# schema.graphqls
type Person {id: ID!name: String!age: Intdept: Dept
}type Dept {id: ID!code: String!name: String!
}
@SchemaMapping(typeName = "Person", field = "dept")
public Dept dept(Person person) {return personService.getDeptByPersonId(person.getId());
}
  • typeName:在上面的示例中,typeName 被设置为 "Person",表示这个方法是处理 Person 类型
  • fieldfield 被设置为 "dept",表示这个方法是处理 Person 类型中的 dept 字段
  • 如上 typeNamefield 属性可以省略,若省略则根据 方法参数类型 推断typeNamePerson,根据 方法名 推断fielddept

@QueryMapping@MutationMapping可以看作是特殊的@SchemaMapping,具体对应关系如下表:

注解说明适用范围
@SchemaMapping需要自定义typeName和field属性均适用,通常用于type类型(对象类型)中的field映射
@QueryMappingtypeName固定为Query,仅需自定义field属性适用于type Query { ... } 中方法映射
@MutationMappingtypeName固定为Mutation,仅需自定义field属性适用type Mutation { ... } 中方法映射

四、单元测试

PersonControllerTest 单元测试类:

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.graphql.GraphQlTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.graphql.test.tester.GraphQlTester;import java.util.Arrays;import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.when;@GraphQlTest({PersonController.class})
public class PersonControllerTest {@MockBeanprivate PersonService personService;@Autowiredprivate GraphQlTester graphQlTester;@Testpublic void testPersonQuery_withQueryStr() {Person person = new Person();person.setId("1");person.setName("John Doe");person.setAge(30);when(personService.getPersonById("1")).thenReturn(person);//document对应查询文本graphQlTester.document("{ person(id: \"1\") { id name age } }").execute().path("person").entity(Person.class).satisfies(p -> {assertEquals("1", p.getId());assertEquals("John Doe", p.getName());assertEquals(30, p.getAge());});}@Testpublic void testPersonQuerySimple() {Person person = new Person();person.setId("1");person.setName("John Doe");person.setAge(30);when(personService.getPersonById("1")).thenReturn(person);//documentName对应查询文本文件名(位于src/text/resources/graphql-test下)graphQlTester.documentName("person_simple").execute().path("person").entity(Person.class).satisfies(p -> {assertEquals("1", p.getId());assertEquals("John Doe", p.getName());assertEquals(30, p.getAge());});}@Testpublic void testPersonQueryComplex() {Person person = new Person();person.setId("1");person.setName("John Doe");person.setAge(30);Dept dept = new Dept();dept.setId("dept_001");dept.setCode("001");dept.setName("dept001");when(personService.getPersonById("1")).thenReturn(person);when(personService.getDeptByPersonId("1")).thenReturn(dept);graphQlTester.documentName("person_complex").execute().path("person").entity(Person.class).satisfies(p -> {assertEquals("1", p.getId());assertEquals("John Doe", p.getName());assertEquals(30, p.getAge());Dept dept1 = p.getDept();assertNotNull(dept1);assertEquals("dept_001", dept1.getId());assertEquals("001", dept1.getCode());assertEquals("dept001", dept1.getName());});}@Testpublic void testPeopleQuery() {Person person1 = new Person();person1.setId("1");person1.setName("John Doe");person1.setAge(30);Person person2 = new Person();person2.setId("2");person2.setName("Jane Doe");person2.setAge(25);when(personService.getAllPeople()).thenReturn(Arrays.asList(person1, person2));graphQlTester.documentName("people").execute().path("people").entityList(Person.class).satisfies(people -> {assertEquals(2, people.size());assertEquals("1", people.get(0).getId());assertEquals("John Doe", people.get(0).getName());assertEquals(30, people.get(0).getAge());assertEquals("2", people.get(1).getId());assertEquals("Jane Doe", people.get(1).getName());assertEquals(25, people.get(1).getAge());});}@Testpublic void testAddPersonMutation() {PersonInput input = new PersonInput();input.setName("John Doe");input.setAge(30);Person person = new Person();person.setId("1");person.setName("John Doe");person.setAge(30);when(personService.addPerson(input)).thenReturn(person);graphQlTester.documentName("addPerson").execute().path("addPerson").entity(Person.class).satisfies(p -> {assertEquals("1", p.getId());assertEquals("John Doe", p.getName());assertEquals(30, p.getAge());});}
}

如上测试类中注意区分graphQLTester.documentgraphQLTester.documentName,其中document方法的参数为query字符串,而documentName对应query文件名(不包括文件类型,如people.graphql即对应people),文件内容即为具体的query字符串,dcoumentName参数中指定的文件位于src/test/resources/graphql-test目录下,如下图:
在这里插入图片描述

具体的query文件内容如下:

person_simple.graphql

{person(id: "1") {idnameage}
}

person_complex.graphql

{person(id: "1") {idnameagedept {idcodename}}
}

people.graphql

{people {idnameage}
}

addPerson.graphql

mutation {addPerson(input: { name: "John Doe", age: 30 }) {idnameage}
}

五、实际 HTTP 请求测试

可以使用 Apifox、Postman 或 cURL 来测试 GraphQL API。

5.1 查询单个 Person

注:
/graphql查询请求的method为 POST
查询参数以JSON请求体进行传输,
且注意 双引号需要进行转义

在这里插入图片描述
具体请求参数:

{"query": "{ person(id: \"48afdb68-0dcb-457b-ba55-bb2750e35b82\") { id name age dept { id code name } } }"
}

亦可替换为(添加query前缀,后续query方法同):

{"query": "query { person(id: \"48afdb68-0dcb-457b-ba55-bb2750e35b82\") { id name age dept { id code name } } }"
}

5.2 查询所有 People

在这里插入图片描述
具体请求参数:

{"query": "{ people { id name age } }"
}

5.3 添加 Person

在这里插入图片描述

具体请求参数:

{"query": "mutation { addPerson(input: { name: \"Alice\", age: 28 }) { id name age dept { id code name } } }"
}

六、其他

6.1 开启graphiql

在application.yaml配置文件中开启graphiql相关配置:

spring:graphql:# 启用graphiqlgraphiql:enabled: truepath: /graphiql

之后即可访问该/graphiql端点进行graphql调试:
在这里插入图片描述

6.2 开启schema查看端点

在application.yaml配置文件中开启schema查看端点相关配置:

spring:graphql:# schema相关配置schema:# 启用接口/graphql/schema - 查询schema定义printer:enabled: true

之后即可访问该/graphql/schema端点查看schema定义:
在这里插入图片描述


参考:
https://docs.spring.io/spring-boot/reference/web/spring-graphql.html
https://docs.spring.io/spring-boot/reference/testing/spring-boot-applications.html#testing.spring-boot-applications.spring-graphql-tests
https://docs.spring.io/spring-graphql/reference/controllers.html
https://github.com/spring-projects/spring-graphql/tree/1.0.x/samples/webmvc-http

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/462226.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Golang | Leetcode Golang题解之第526题优美的排列

题目&#xff1a; 题解&#xff1a; func countArrangement(n int) int {f : make([]int, 1<<n)f[0] 1for mask : 1; mask < 1<<n; mask {num : bits.OnesCount(uint(mask))for i : 0; i < n; i {if mask>>i&1 > 0 && (num%(i1) 0 |…

模拟栈的实现

栈的概念 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈 顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出 LIFO &#xff08; Last In First Out &#xff09;的原则。 压栈&…

Win10搭建SFTP服务器

1、下载安装 Release v9.5.0.0p1-Beta PowerShell/Win32-OpenSSH GitHub 下载OpenSSH-Win64.zip 解压之后放入到&#xff1a;C:\Program Files (x86)\OpenSSH-Win64以管理员身份打开CMD进入到 C:\Program Files (x86)\OpenSSH-Win64 文件夹执行命令 powershell.exe -Exec…

WordPress网站添加嵌入B站视频,自适应屏幕大小,取消自动播放

结合bv号 改成以下嵌入式代码&#xff08;自适应屏幕大小,取消自动播放&#xff09; <iframe style"width: 100%; aspect-ratio: 16/9;" src"//player.bilibili.com/player.html?isOutsidetrue&bvidBV13CSVYREpr&p1&autoplay0" scrolling…

C语言内幕--全局变量(结合内存分区、汇编视角看类型、连接器)

前言 学习资源&#xff1a;b站up主&#xff1a;底层技术栈学过C语言都知道&#xff0c;全局变量可以再全局中使用&#xff0c;其实全局变量内部还是涉及到不少知识&#xff0c;这里从内存分区、汇编视角看类型、连接器等角度看待全局变量&#xff1b;由于涉及到底层技术&#…

省级-建成区绿化覆盖率数据(2006-2022年)

建成区绿化覆盖率是指城市建成区的绿化覆盖面积占建成区的百分比。 城市绿化覆盖率的提升&#xff0c;不仅能够改善城市的空气质量&#xff0c;降低噪音污染&#xff0c;还能提高城市的生物多样性&#xff0c;为市民提供更多的休闲和娱乐空间。 2006年-2022年省级-建成区绿化…

基于CNN-BiLSTM的时间序列数据预测,15个输入1个输出,可以更改数据集,MATLAB代码

1. 数据收集与预处理 数据清洗&#xff1a;处理缺失值、异常值等。特征工程&#xff1a;提取有助于预测的特征。数据标准化&#xff1a;将时间序列数据标准化&#xff0c;使其具有零均值和单位方差&#xff0c;有助于模型训练。滑动窗口划分&#xff1a;将时间序列数据划分为多…

SSM学习 day02

一、vue项目开发流程 vue根组件 <template><div><h1>{{ message }}</h1><element-view></element-view></div> </template><script> import ElementView from ./views/Element/ElementView.vue export default {compon…

【NOIP普及组】 FBI树

【NOIP普及组】 FBI树 C语言版本C 版本Java版本Python版本 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 我们可以把由“0”和“1”组成的字符串分为三类&#xff1a;全“0”串称为B串&#xff0c;全“1”串称为I串&#xff0c;既含“0”又…

大数据新视界 -- 大数据大厂之数据质量管理全景洞察:从荆棘挑战到辉煌策略与前沿曙光

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

基于 webpack 项目接入 vite 你可能需要注意的点

前言 在之前的 如何优化你的 vue-cli 项目&#xff1f; 一文中介绍基于 webpack 进行的一些优化方法&#xff0c;本文的核心是基于一个 vue2 的项目&#xff08;也就是上篇文章中的项目&#xff09;来继续介绍一下如何接入 vite&#xff0c;以及这个过程中需要关注的点。 之前…

Python工具箱系列(五十七)

图像分割与人脸识别 众所周知图像是由若干有意义的像素组成的&#xff0c;图像分割作为计算机视觉的基础&#xff0c;对具有现有目标和较精确边界的图像进行分割&#xff0c;实现在图像像素级别上的分类任务。图像分割可分为语义分割和实例分割两类&#xff0c;区别如下&#x…

日志代码编写

&#x1f30e;日志代码编写 文章目录&#xff1a; 日志代码编写 了解日志 日志编写       日志等级       获取时间信息       获取文件名行号及处理可变参数列表       以宏的形式传参       日志加锁       日志消息输出方式 完整代码 …

告别繁琐统计,一键掌握微信数据

微信数据管理的挑战在数字时代&#xff0c;微信已成为我们日常沟通和商业活动的重要工具。然而&#xff0c;随着微信号数量的增加&#xff0c;手动统计每个账号的数据变得越来越繁琐。从好友数量到会话记录&#xff0c;再到转账和红包&#xff0c;每一项都需要耗费大量的时间和…

【第几小】

题目 代码 //分块可以AC 20个点的块长&#xff0c; sqrt(n)*5#include<bits/stdc.h> using namespace std;int main(){ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);int n; cin>>n;vector<int> a(n1,0);//分块int len sqrt(n)*5; //块长int k (n%len…

详细分析Pytorch中的transpose基本知识(附Demo)| 对比 permute

目录 前言1. 基本知识2. Demo 前言 原先的permute推荐阅读&#xff1a;详细分析Pytorch中的permute基本知识&#xff08;附Demo&#xff09; 1. 基本知识 transpose 是 PyTorch 中用于交换张量维度的函数&#xff0c;特别是用于二维张量&#xff08;矩阵&#xff09;的转置操…

使用Docker构建和部署微服务

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 [TOC] Docker 是一个开源的容器化平台&#xff0c;可以帮助开发者轻松构建、打包和部署应用程序。本文将详细介绍如何使用 Dock…

Python+Appium+Pytest+Allure自动化测试框架-代码篇

文章目录 自动化测试框架工程目录示例测试代码示例结果查看allurepytest编写pytest测试样例的规则pytest conftest.py向测试函数传参 appium启动appium服务代码端通过端口与appium服务通信对设备进行操作在pytest测试用例中调用appium 更多功能 PythonAppiumPytestAllure自动化…

Elasticsearch Interval 查询:为什么它们是真正的位置查询,以及如何从 Span 转换

作者&#xff1a;来自 Elastic Mayya Sharipova 解释 span 查询如何成为真正的位置查询以及如何从 span 查询过渡到它们。 长期以来&#xff0c;Span 查询一直是有序和邻近搜索的工具。这些查询对于特定领域&#xff08;例如法律或专利搜索&#xff09;尤其有用。但相对较新的 …

IoTDB时序数据库使用

简介 Apache IoTDB 是一款低成本、高性能的物联网原生时序数据库。它可以解决企业组建物联网大数据平台管理时序数据时所遇到的应用场景复杂、数据体量大、采样频率高、数据乱序多、数据处理耗时长、分析需求多样、存储与运维成本高等多种问题。 IoTDB官网 1. 连接数据库 官方…