GraphQL基础知识与Spring for GraphQL使用教程

文章目录

GraphQL是一种用于API开发的查询语言和运行时环境。它由Facebook开发并于2015年开源。GraphQL的主要目标是提供一种更高效、灵活和易于使用的方式来获取和操作数据。与传统的RESTful API相比,GraphQL允许客户端精确地指定需要的数据,并减少了不必要的网络传输和数据处理。
采用GraphQL,甚至不需要有任何的接口文档,在定义了Schema之后,服务端实现Schema,客户端可以查看Schema,然后构建出自己需要的查询请求来获得自己需要的数据。

1、数据类型

1.1、标量类型

  1. Int -32位整型数字;
  2. Float-双精度浮点型;
  3. String-UTF‐8 字符序列;
  4. Boolean-布尔型,true 或者 false;
  5. ID-标识类型,唯一标识符,注意此ID为字符,如果使用Mysql自增长id,也会自动转为对应的字符串;

1.2. 高级数据类型

  1. Object - 对象,用于描述层级或者树形数据结构。Object类型有一个类型名,以及类型包含的字段。

    type Product {
    id: ID!
    info: String!
    price: Float
    }

在此示例中,声明了Product对象类型,定义了3 个字段:

id:非空 ID 类型。

info:非空字符串类型。

price:浮点型。

  1. Interface-接口,用于描述多个类型的通用字;与 Object一样。

    interface Product {
    id: ID!
    info: String!
    price: Float
    }

  2. Union-联合类型,用于描述某个字段能够支持的所有返回类型以及具体请求真正的返回类型;

  3. Enum-枚举,用于表示可枚举数据结构的类型;

    enum Status {
    Yes
    No
    }
    type Product {
    id: ID!
    info: String!
    price: Float
    stat: Status
    }

  4. Input-输入类型input本质上也是一个type类型,是作为Mutation接口的输入参数。换言之,想要定义一个修改接口(add,update,delete)的输入参数对象,就必须定义一个input输入类型。

    input BookInput {
    isbn: ID!
    title: String!
    pages: Int
    authorIdCardNo: String
    }

  5. List -列表,任何用方括号 ([]) 括起来的类型都会成为 List 类型。

    type Product {
    id: ID!
    info: String
    price: Float
    images: [String]
    }

  6. Non-Null-不能为 Null,类型后边加!表示非空类型。例如,String 是一个可为空的字符串,而String!是必需的字符串。

基本操作

  • Query(只读操作)

    #schema.graphqls定义操作
    type Query {
    allBooks: [Book]!
    bookByIsbn(isbn: ID): Book
    }

    接口查询语法

    query{
    allBooks {
    title
    author {
    name
    age
    }
    }
    }

  • Mutation(可写操作)

    #schema.graphqls定义操作
    type Mutation {
    createBook(bookInput: BookInput): Book
    createAuthor(authorInput: AuthorInput): Author
    }

    mutation{

    createAuthor(authorInput:{

    idCardNo: "341234567891234567",

    name:"test1",

    age:38

    }

    ){

    name

    age

    }

    }

  • Subscription(订阅操作)

    type Subscription {
    greetings: String
    }

2、Spring for GraphQL实例

GraphQL只是一种架构设计,具体的实现需要各个技术平台自己实现,目前主流的开发语言基本都已经有现成的类库可以使用,GraphQL Java就是Java平台的实现。

GraphQL Java虽然实现了GraphQL,但是只是一个执行GraphQL请求的引擎,用户在使用中需要创建自己的HTTP服务来提供服务。

Spring for GraphQL为基于GraphQL Java构建的Spring应用程序提供支持,来自 GraphQL Java 团队,它的目标是成为所有Spring、GraphQL应用程序的基础。

spring-graphql中定义的核心注解如下:

  • @GraphQlController:申明该类是GraphQL应用中的控制器
  • @QueryMapping:申明该类或方法使用GraphQL的query操作,等同于@SchemaMapping(typeName = "Query"),类似于@GetMapping
  • @Argument:申明该参数是GraphQL应用的入参
  • @MutationMapping:申明该类或方法使用GraphQL的mutation操作,等同于@SchemaMapping(typeName = "Mutation")
  • @SubscriptionMapping:申明该类或方法使用GraphQL的subscription操作,等同于@SchemaMapping(typeName = "Subscription")
  • @SchemaMapping:指定GraphQL操作类型的注解,类似@RequestMapping

2.1、项目目录

项目代码目录

2.2、数据库表

数据表结构,这里例子简单用了用户表和用户日志表

sql 复制代码
CREATE TABLE `admin_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4
    
CREATE TABLE `admin_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` varchar(255) DEFAULT NULL,
  `visit_url` varchar(255) DEFAULT NULL,
  `create_date` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4

2.3、GraphQL的schema.graphql

GraphQL对应的schema.graphql定义文件,注意GraphQL默认只支持标量类型,DateTime自定义类型使用graphql-java-extended-scalars:https://github.com/graphql-java/graphql-java-extended-scalars提供

scalar DateTime

type AdminUser{
    id: ID!
    name: String!
}

type AdminLog{
    id: ID!
    visitUrl: String
    user: AdminUser!
    createDate: DateTime
}

type Query {
    allLogs:[AdminLog]
    logByUser(userid: ID): [AdminLog]
}

type Mutation {
    createUser(adminUserInput: AdminUserInput): AdminUser
    createLog(adminLogInput: AdminLogInput): AdminLog
}


input AdminLogInput {
    userId: String!
    visitUrl: String
    createDate: DateTime
}

input AdminUserInput {
    name: String!
}

type Subscription {
    greetings: String
}

2.4、Java代码

pom.xml依赖包文件

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.0.10</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.penngo.example</groupId>
	<artifactId>graphql-app</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>graphql-app</name>
	<description>graphql-app project for Spring Boot</description>
	<properties>
		<java.version>17</java.version>
		<maven.compiler.source>17</maven.compiler.source>
		<maven.compiler.target>17</maven.compiler.target>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-graphql</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.23</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/com.graphql-java/graphql-java-extended-scalars -->
		<dependency>
			<groupId>com.graphql-java</groupId>
			<artifactId>graphql-java-extended-scalars</artifactId>
			<version>19.1</version>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>
	<repositories>
		<repository>
			<id>alimaven</id>
			<name>Maven Aliyun Mirror</name>
			<url>https://maven.aliyun.com/repository/central</url>
		</repository>
	</repositories>

	<pluginRepositories>
		<pluginRepository>
			<id>aliyun-plugin</id>
			<url>https://maven.aliyun.com/repository/public</url>
			<releases>
				<enabled>true</enabled>
			</releases>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</pluginRepository>
	</pluginRepositories>

</project>

对应的数据库实体类

java 复制代码
package com.penngo.example.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.OffsetDateTime;

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AdminLog {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Long id;
    private String userId;
    private String visitUrl;
    private OffsetDateTime createDate;
}

package com.penngo.example.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AdminUser {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Long id;
    private String name;
}

GraphQL对应的输入类

package com.penngo.example.entity;
import lombok.Data;
import java.time.OffsetDateTime;
@Data
public class AdminLogInput {
    private String userId;
    private String visitUrl;
    private OffsetDateTime createDate;

}

package com.penngo.example.entity;
import lombok.Data;
@Data
public class AdminUserInput {
    private String name;
}

对应的数据库操作类

java 复制代码
package com.penngo.example.repository;
import com.penngo.example.entity.AdminUser;
import org.springframework.data.jpa.repository.JpaRepository;
public interface AdminUserRepository  extends JpaRepository<AdminUser,Long> {
    AdminUser findById(String userId);
}

package com.penngo.example.repository;
import com.penngo.example.entity.AdminLog;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;

public interface AdminLogRepository  extends JpaRepository<AdminLog,Long> {
    List<AdminLog> findAllByUserId(String userId);
}

对外服务接口类

java 复制代码
package com.penngo.example.controller;
import com.penngo.example.entity.*;
import com.penngo.example.repository.AdminLogRepository;
import com.penngo.example.repository.AdminUserRepository;
import org.springframework.beans.BeanUtils;
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 AdminLogController {
    private final AdminUserRepository adminUserRepository;
    private final AdminLogRepository adminLogRepository;

    public AdminLogController(AdminUserRepository adminUserRepository, AdminLogRepository adminLogRepository){
        this.adminLogRepository = adminLogRepository;
        this.adminUserRepository = adminUserRepository;
    }
    @QueryMapping
    public List<AdminLog> allLogs(){
        List<AdminLog> logsList = adminLogRepository.findAll();
        return logsList;
    }
    @QueryMapping
    public List<AdminLog> logByUser(@Argument String userid){
        return adminLogRepository.findAllByUserId(userid);
    }

    @SchemaMapping(typeName = "AdminLog" ,field = "user")
    public AdminUser getAuthor(AdminLog adminLog){
        AdminUser adminUser = adminUserRepository.findById(adminLog.getUserId());
        return adminUser;
    }
    @MutationMapping
    public AdminLog createLog(@Argument AdminLogInput adminLogInput){
        AdminLog log = new AdminLog();
        BeanUtils.copyProperties(adminLogInput,log);
        return adminLogRepository.save(log);
    }
}

package com.penngo.example.controller;
import com.penngo.example.entity.*;
import com.penngo.example.repository.AdminLogRepository;
import com.penngo.example.repository.AdminUserRepository;
import org.springframework.beans.BeanUtils;
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.stereotype.Controller;
import java.util.List;

@Controller
public class AdminUserController {
    private final AdminUserRepository adminUserRepository;
    private final AdminLogRepository adminLogRepository;

    public AdminUserController(AdminUserRepository adminUserRepository, AdminLogRepository adminLogRepository){
        this.adminLogRepository = adminLogRepository;
        this.adminUserRepository = adminUserRepository;
    }
    @QueryMapping
    public List<AdminLog> userById(@Argument String userid){
        return adminLogRepository.findAllByUserId(userid);
    }
    @MutationMapping
    public AdminUser createUser(@Argument AdminUserInput adminUserInput){
        AdminUser user = new AdminUser();
        BeanUtils.copyProperties(adminUserInput,user);
        return adminUserRepository.save(user);
    }
}

package com.penngo.example.controller;
import com.penngo.example.entity.AdminLog;
import com.penngo.example.repository.AdminLogRepository;
import com.penngo.example.repository.AdminUserRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.graphql.data.method.annotation.SubscriptionMapping;
import org.springframework.stereotype.Controller;
import reactor.core.publisher.Flux;
import java.time.Duration;

@Controller
public class GreetingController {
   private final AdminUserRepository adminUserRepository;
   private final AdminLogRepository adminLogRepository;

   public GreetingController(AdminUserRepository adminUserRepository, AdminLogRepository adminLogRepository){
      this.adminLogRepository = adminLogRepository;
      this.adminUserRepository = adminUserRepository;
   }
   // 数据订阅,取最新的5条数据,每5秒发送一条给客户端,一共5次
   @SubscriptionMapping
   public Flux<AdminLog> greetings(){
      System.out.println("greetings====================");
      Page<AdminLog> logsList = adminLogRepository.findAll(PageRequest.of(0,5).withSort(Sort.Direction.DESC, "id"));
      return Flux.fromStream(logsList.stream())
              .delayElements(Duration.ofSeconds(5))
              .take(5);
   }
}

自定义日期数据类型DateTime

package com.penngo.example.component;
import graphql.scalars.ExtendedScalars;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.graphql.execution.RuntimeWiringConfigurer;

@Configuration
public class CustomScalarType {
    @Bean
    public RuntimeWiringConfigurer runtimeWiringConfigurer() {
        return wiringBuilder -> wiringBuilder.scalar(ExtendedScalars.DateTime);
    }
}

服务启动类

java 复制代码
package com.penngo.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class GraphqlAppApplication {
	public static void main(String[] args) {
		SpringApplication.run(GraphqlAppApplication.class, args);
	}
}

3、运行效果

3.1、添加用户

添加用户

mutation{ 
  createUser(adminUserInput: { 
    name: "test1", 
  } ) 
  { 
    id 
    name 
  } }

3.2、添加日志

添加日志

graphql 复制代码
mutation{
   createLog(adminLogInput: {
      userId: "1",
      visitUrl: "http://localhost:8080/method1"
      createDate: "2023-09-17T19:39:57+08:00"
   } )
   {
      id
      visitUrl
      createDate
   } }  

3.3、查询所有日志

查询所有日志

graphql 复制代码
query{
  allLogs{
    id
    visitUrl
    user{
    	id
    	name
    }
    createDate
  }
}

3.4、查询指定用户日志

查询指定用户日志

graphql 复制代码
query{
  logByUser(userid:"1") {
    id
    visitUrl
    user{
    	id
    	name
    }
    createDate
  }
}

3.5、数据订阅

数据订阅,订阅需要有websocket的支持。

graphql 复制代码
subscription {
  greetings
}


4、总结

使用Spring for GraphQL试用了GraphQL后,它实现按需取数据的功能。服务器开发人员和前端开发人员可以通过schema.graphqls定义文件,协定好接口和数据,省掉写接口文档的工作。

缺点可能就是需要一点学习成本,虽然提供数据嵌套可以通过一个请求获取所有数据,但是嵌套复杂可能引起性能问题。

Spring for GraphQL官方参考:https://docs.spring.io/spring-graphql/docs/current/reference/html/#overview

相关推荐
0zxm1 小时前
06 - Django 视图view
网络·后端·python·django
m0_748257181 小时前
Spring Boot FileUpLoad and Interceptor(文件上传和拦截器,Web入门知识)
前端·spring boot·后端
小_太_阳2 小时前
Scala_【1】概述
开发语言·后端·scala·intellij-idea
智慧老师2 小时前
Spring基础分析13-Spring Security框架
java·后端·spring
搬码后生仔3 小时前
asp.net core webapi项目中 在生产环境中 进不去swagger
chrome·后端·asp.net
凡人的AI工具箱3 小时前
每天40分玩转Django:Django国际化
数据库·人工智能·后端·python·django·sqlite
Lx3524 小时前
Pandas数据重命名:列名与索引为标题
后端·python·pandas
小池先生4 小时前
springboot启动不了 因一个spring-boot-starter-web底下的tomcat-embed-core依赖丢失
java·spring boot·后端
百罹鸟4 小时前
【vue高频面试题—场景篇】:实现一个实时更新的倒计时组件,如何确保倒计时在页面切换时能够正常暂停和恢复?
vue.js·后端·面试
hanbarger5 小时前
mybatis框架——缓存,分页
java·spring·mybatis