• 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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在此示例中,声明了Product对象类型,定义了3 个字段:
    id:非空 ID 类型。
    info:非空字符串类型。
    price:浮点型。

    1. Interface-接口,用于描述多个类型的通用字;与 Object一样。
    interface Product {
        id: ID!
        info: String!
        price: Float
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. Union-联合类型,用于描述某个字段能够支持的所有返回类型以及具体请求真正的返回类型;
    2. Enum-枚举,用于表示可枚举数据结构的类型;
    enum Status {
      Yes
      No
    }
    type Product {
        id: ID!
        info: String!
        price: Float
        stat: Status
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. Input-输入类型input本质上也是一个type类型,是作为Mutation接口的输入参数。换言之,想要定义一个修改接口(add,update,delete)的输入参数对象,就必须定义一个input输入类型。
    input BookInput {
        isbn: ID!
        title: String!
        pages: Int
        authorIdCardNo: String
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. List -列表,任何用方括号 ([]) 括起来的类型都会成为 List 类型。
    type Product {
        id: ID!
        info: String
        price: Float
        images: [String]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. Non-Null-不能为 Null,类型后边加!表示非空类型。例如,String 是一个可为空的字符串,而String!是必需的字符串。

    基本操作

    • Query(只读操作)
    #schema.graphqls定义操作
    type Query {
        allBooks: [Book]!
        bookByIsbn(isbn: ID): Book
    }
    
    # 接口查询语法
    query{
      allBooks {
        title
        author {
          name
          age
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • Mutation(可写操作)
    #schema.graphqls定义操作
    type Mutation {
        createBook(bookInput: BookInput): Book
        createAuthor(authorInput: AuthorInput): Author
    }
    
    # mutation{
    #   createAuthor(authorInput:{ 
    #     idCardNo: "341234567891234567",
    #     name:"test1",
    #     age:38
    #   }
    #   ){
    #     name 
    #     age
    #   }
    # }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • Subscription(订阅操作)
    type Subscription {
        greetings: String
    }
    
    • 1
    • 2
    • 3

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

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

    2.4、Java代码

    pom.xml依赖包文件

    
    <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.0modelVersion>
    	<parent>
    		<groupId>org.springframework.bootgroupId>
    		<artifactId>spring-boot-starter-parentartifactId>
    		<version>3.0.10version>
    		<relativePath/> 
    	parent>
    	<groupId>com.penngo.examplegroupId>
    	<artifactId>graphql-appartifactId>
    	<version>0.0.1-SNAPSHOTversion>
    	<name>graphql-appname>
    	<description>graphql-app project for Spring Bootdescription>
    	<properties>
    		<java.version>17java.version>
    		<maven.compiler.source>17maven.compiler.source>
    		<maven.compiler.target>17maven.compiler.target>
    		<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
    	properties>
    	<dependencies>
    		<dependency>
    			<groupId>org.springframework.bootgroupId>
    			<artifactId>spring-boot-starter-data-jpaartifactId>
    		dependency>
    		<dependency>
    			<groupId>org.springframework.bootgroupId>
    			<artifactId>spring-boot-starter-graphqlartifactId>
    		dependency>
    		<dependency>
    			<groupId>org.springframework.bootgroupId>
    			<artifactId>spring-boot-starter-webartifactId>
    		dependency>
    		<dependency>
    			<groupId>org.projectlombokgroupId>
    			<artifactId>lombokartifactId>
    			<optional>trueoptional>
    		dependency>
    		<dependency>
    			<groupId>mysqlgroupId>
    			<artifactId>mysql-connector-javaartifactId>
    			<version>8.0.23version>
    		dependency>
    		
    		<dependency>
    			<groupId>com.graphql-javagroupId>
    			<artifactId>graphql-java-extended-scalarsartifactId>
    			<version>19.1version>
    		dependency>
    	dependencies>
    	<build>
    		<plugins>
    			<plugin>
    				<groupId>org.springframework.bootgroupId>
    				<artifactId>spring-boot-maven-pluginartifactId>
    				<configuration>
    					<excludes>
    						<exclude>
    							<groupId>org.projectlombokgroupId>
    							<artifactId>lombokartifactId>
    						exclude>
    					excludes>
    				configuration>
    			plugin>
    		plugins>
    	build>
    	<repositories>
    		<repository>
    			<id>alimavenid>
    			<name>Maven Aliyun Mirrorname>
    			<url>https://maven.aliyun.com/repository/centralurl>
    		repository>
    	repositories>
    
    	<pluginRepositories>
    		<pluginRepository>
    			<id>aliyun-pluginid>
    			<url>https://maven.aliyun.com/repository/publicurl>
    			<releases>
    				<enabled>trueenabled>
    			releases>
    			<snapshots>
    				<enabled>falseenabled>
    			snapshots>
    		pluginRepository>
    	pluginRepositories>
    
    project>
    
    • 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
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89

    对应的数据库实体类

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

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    对应的数据库操作类

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    对外服务接口类

    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);
       }
    }
    
    
    • 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
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108

    自定义日期数据类型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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    服务启动类

    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);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3、运行效果

    3.1、添加用户

    添加用户

    mutation{ 
      createUser(adminUserInput: { 
        name: "test1", 
      } ) 
      { 
        id 
        name 
      } }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述

    3.2、添加日志

    添加日志

    mutation{
       createLog(adminLogInput: {
          userId: "1",
          visitUrl: "http://localhost:8080/method1"
          createDate: "2023-09-17T19:39:57+08:00"
       } )
       {
          id
          visitUrl
          createDate
       } }  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述

    3.3、查询所有日志

    查询所有日志

    query{
      allLogs{
        id
        visitUrl
        user{
        	id
        	name
        }
        createDate
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述

    3.4、查询指定用户日志

    查询指定用户日志

    query{
      logByUser(userid:"1") {
        id
        visitUrl
        user{
        	id
        	name
        }
        createDate
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述

    3.5、数据订阅

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

    subscription {
      greetings
    }
    
    • 1
    • 2
    • 3

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

    4、总结

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

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

  • 相关阅读:
    JVM G1收集器几个重要的概念
    《C++ Primer》第5章 语句
    java毕业设计在线拍卖系统Mybatis+系统+数据库+调试部署
    DataGrip数据仓库工具
    go语言基础笔记
    docker基础命令操作---镜像操作
    面试被经常问的SQL窗口函数!
    Kubernetes基于helm部署Kafka_Kraft集群并取消SASL认证且开启数据持久化
    minio集群部署(k8s内)
    【面经】2022社招软件测试面试(6)-德科
  • 原文地址:https://blog.csdn.net/penngo/article/details/132957429