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

RPC选型入门测试系列文章
【1】GraphQL基础知识与Spring for GraphQL使用教程
【2】gRPC Java、Go、PHP使用例子
【3】Thrift RPC Java、Go、PHP使用例子


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
}
  1. Union-联合类型,用于描述某个字段能够支持的所有返回类型以及具体请求真正的返回类型;
  2. Enum-枚举,用于表示可枚举数据结构的类型;
enum Status {
  Yes
  No
}
type Product {
    id: ID!
    info: String!
    price: Float
    stat: Status
}
  1. Input-输入类型input本质上也是一个type类型,是作为Mutation接口的输入参数。换言之,想要定义一个修改接口(add,update,delete)的输入参数对象,就必须定义一个input输入类型。
input BookInput {
    isbn: ID!
    title: String!
    pages: Int
    authorIdCardNo: String
}
  1. List -列表,任何用方括号 ([]) 括起来的类型都会成为 List 类型。
type Product {
    id: ID!
    info: String
    price: Float
    images: [String]
}
  1. 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、数据库表

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

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 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>

对应的数据库实体类

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;
}

对应的数据库操作类

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);
}

对外服务接口类

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);
    }
}

服务启动类

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、添加日志

添加日志

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

在这里插入图片描述

3.3、查询所有日志

查询所有日志

query{
  allLogs{
    id
    visitUrl
    user{
    	id
    	name
    }
    createDate
  }
}

在这里插入图片描述

3.4、查询指定用户日志

查询指定用户日志

query{
  logByUser(userid:"1") {
    id
    visitUrl
    user{
    	id
    	name
    }
    createDate
  }
}

在这里插入图片描述

3.5、数据订阅

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

subscription {
  greetings
}

在这里插入图片描述
在这里插入图片描述

4、总结

使用Spring for GraphQL试用了GraphQL后,它实现按需取数据的功能。服务器开发人员和前端开发人员可以通过schema.graphqls定义文件,协定好接口和数据,省掉写接口文档的工作。
缺点可能就是需要一点学习成本,虽然提供数据嵌套可以通过一个请求获取所有数据,但是嵌套复杂可能引起性能问题。

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

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

penngo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值