目录

历史

是什么?

GraphQL和RESTful区别

区别说明:

举个使用场景说明两者差异:

RESTful实现方式:

GraphQL实现方式:

有什么用?

谁在用?

怎么用?

了解GrapQL规范

字段(Fields)

参数(Arguments)

别名(Aliases)

片段(Fragments)

Schema 和类型

标量类型(Scalar Types)

枚举类型(Enumeration Types)

接口(Interfaces)

创建第一个GraphQL java项目

打开idea新建一个springboot项目

引入graphql-java maven依赖

引入lombok,注解生成getset

编写第一个demo程序

优化项目中GraphQL查询

SDL查询

http接口查询

(推荐)graphql-java-kickstart实现graphQL

官网地址:

与RESTful实现差异对比

引入graph maven依赖

项目pom.xml

编写第一个demo程序

参考网址

项目中遇到的问题

graphql增删改跨域问题

springboot2.6pagehelper循环依赖

总结


历史

        GraphQL是由Facebook创造的

        当时,Facebook想在移动端实现新闻推送,这不像检索一个故事、作者、故事的内容、评论列表和喜欢该文章的人这么简单,而是每个故事都相互关联、嵌套和递归的。现有的API没有被设计成允许开发人员在移动设备上展示一个丰富、类似新闻推送的体验。它们没有层次性,允许开发人员选择他们所需要的,或有显示异构推送故事列表的能力。因此,2012年Facebook决定自己构建一个新的新闻推送API,这就是GraphQL形成时间。同年 8 月中旬,Facebook 发布了采用新 GraphQL 技术的 iOS5.0 应用程序。它允许开发人员通过利用其数据获取(data-fetching)功能来减少网络的使用。在接下来的一年半时间里,除了新闻推送外,GraphQL API 扩展到大多数的 FacebookiOS 应用程序。在 2015 年,GraphQL 规范首次与 JavaScript 中的引用实现一起发布

参考网址:

GraphQL 的前世今生 - .NET西安社区 - 博客园

谈谈 GraphQL 的历史、组件和生态系统 - 知乎

是什么?

官网地址:GraphQL | A query language for your API

官网解释

        一种用于 API 的查询语言

        GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。 GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具。

通俗解释:

        和常规sql查询语言不同,它是一种用于前后端数据查询方式的规范。本质就是API查询语言,开发人员可以自定义查询规则,定义自己所需的数据格式,并且在一个请求中获取所有想要的数据(PS:RESTful可能需要多次请求不同接口)。意思就是定义什么返回什么,开发人员对于返回的结果是可预测的。

GraphQL和RESTful区别

        参考网址:GraphQL和RESTful的区别 - 奔跑的瓜牛 - 博客园

区别说明:

        GraphQL与RESTful都是基于HTTP进行数据的请求与接收。

        核心差异:资源的描述信息与其获取方式相分离。

        例如根据图书id获取图书信息:

       RESTful获取:

GET /books/1
{
  "title": "Black Hole Blues",
  "author": { 
    "firstName": "Janna",
    "lastName": "Levin"
  }
  // ... more fields here
}

GraphQL获取:

#1.首先定义数据类型
type Book {
  id: ID
  title: String
  published: Date
  price: String
  author: Author
}
type Author {
  id: ID
  firstName: String
  lastName: String
  books: [Book]
}
#2.创建query
type Query {
  book(id: ID!): Book
  author(id: ID!): Author
}	

#3.http查询
GET /graphql?query={ book(id: "1") { title, author { firstName } } }
{
  "title": "Black Hole Blues",
  "author": {
    "firstName": "Janna",
  }
}

二者对比:

相同点:

  • 都有资源这个概念,而且都能通过ID去获取资源
  • 都可以通过HTTP GET方式来获取资源
  • 都可以使用JSON作为响应格式

差异点:

  • 在RESTful中,你所访问的路径就是该资源的唯一标识(ID);在GraphQL中,该标识与访问方式并不相关(PS:RESTful url是唯一的,GraphQL一般访问地址是一个,查询query不同)
  • 在RESTful中,资源的返回结构与返回数量是由服务端决定;在GraphQL,服务端只负责定义哪些资源是可用的,由客户端自己决定需要得到什么资源。

举个使用场景说明两者差异:

        为了更好的理解两者的差异,我们用一个场景来说明。比如现在有一个简单的示例场景:在blog应用程序中,应用程序需要显示特定用户的文章的标题。同一屏幕还显示该用户最后3个关注者的名称。REST和GraphQL如何解决这种情况?

RESTful实现方式:

  1. 通过 /user/<id>获取初始用户数据
  2. 通过/user/<id>/posts 返回用户的所有帖子
  1. 请求/user/<id>/followers,返回每个用户的关注者列表

现在有个要求:要获取某个用户信息和用户所有的帖子以及用户关注者信息。

GraphQL实现方式:

结论:RESTful 请求了3次达到目的,并且接口返回了很多并不需要的数据。GraphQL只请求了一次,并且返回的结果是必需的。

有什么用?

官网解释

  1. 请求你所要的数据不多不少。
  2. 获取多个资源只用一个请求。
  3. 描述所有的可能类型系统。
  4. API 演进无需划分版本。

谁在用?

怎么用?

了解GrapQL规范

字段(Fields)

        在GraphQL的查询中,请求结构中包含了所预期结果的结构,这个就是字段。并且响应的结构和请求结构基本一致,这是GraphQL的一个特性,这样就可以让请求发起者很清楚的知道自己想要什么。

参数(Arguments)

        在查询数据时,离不开传递参数,在GraphQL的查询中,也是可以传递参数的,语法∶(参数名:参数值)

别名(Aliases)

        如果一次查询多个相同对象,但是值不同,这个时候就需要起别名了,否则json的语法就不能通过了。

这个时候用到别名,使用方法如下:

片段(Fragments)

        片段使你能够组织一组字段,然后在需要它们的地方引入。下面例子展示了如何使用片段解决上述场景:

Schema 和类型

        Schema是用于定义数据结构的,比如说,User对象中有哪些属性,对象与对象之间是什么关系等。

Schema定义结构:

scalar Long
schema {#定义查询
    query:UserQuery
}

"用户查询" #注释
type UserQuery{#定义查询类型
    "根据id查询用户"
    getUser(userId:Long):UserVO #指定对象以及参数类型
 }

"用户返回对象"
type UserVO{ #定义对象
    "用户id"
    userId:Long! #!表示属性是非空项
    userName:String
    age:Int
    dept:DeptVO
}

type DeptVO{
    deptId:Long
    deptName:String
}

PS:通过双引号的方式来添加注释。

标量类型(Scalar Types)

GraphQL 自带一组默认标量类型:

  • Int:有符号 32 位整数。
  • Float:有符号双精度浮点值。
  • String:UTF‐8 字符序列。
  • Boolean:true 或者 false。
  • ID:ID 标量类型表示一个唯一标识符,通常用以重新获取对象或者作为缓存中的键。ID 类型使用和 String 一样的方式序列化;然而将其定义为 ID 意味着并不需要人类可读型。

大部分的 GraphQL 服务实现中,都有自定义标量类型的方式。例如,我们可以定义一个 Date 类型:

scalar Date

        然后就取决于我们的实现中如何定义将其序列化、反序列化和验证。例如,你可以指定 Date 类型应该总是被序列化成整型时间戳,而客户端应该知道去要求任何 date 字段都是这个格式。

枚举类型(Enumeration Types)

        也称作枚举(enum),枚举类型是一种特殊的标量,它限制在一个特殊的可选值集合内。这让你能够:验证这个类型的任何参数是可选值的某一个与类型系统沟通,一个字段总是一个有限值集合的其中一个值。

下面是一个用 GraphQL schema 语言表示的 enum 定义:

接口(Interfaces)

        跟许多类型系统一样,GraphQL 支持接口。一个接口是一个抽象类型,它包含某些字段,而对象类型必须包含这些字段,才能算实现了这个接口

例如,你可以用一个 Character 接口用以表示《星球大战》三部曲中的任何角色:

interface Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
}

        这意味着任何实现 Character 的类型都要具有这些字段,并有对应参数和返回类型。

例如,这里有一些可能实现了 Character 的类型:

type Human implements Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
  starships: [Starship]
  totalCredits: Int
}
 
type Droid implements Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
  primaryFunction: String
}

        可见这两个类型都具备 Character 接口的所有字段,但也引入了其他的字段 totalCredits、starships 和 primaryFunction,这都属于特定的类型的角色。

创建第一个GraphQL java项目

        根据官网提供的graphql-java来实现的,在实际开发中不推荐使用这种方式,推荐使用graphql-java-kickstart来实现。

打开idea新建一个springboot项目

File->New->Project...

注意:jdk版本是1.8以上,graphql-java要求jdk至少是8。

引入graphql-java maven依赖

可参照graphql-java官网:Getting started | GraphQL Java

<dependency>
    <groupId>com.graphql-java</groupId>
    <artifactId>graphql-java</artifactId>
    <version>16.2</version>
</dependency>

引入lombok,注解生成getset

<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.18.8</version>
</dependency>

编写第一个demo程序

定义查询schame结构

scalar Long
schema {#定义查询
    query:UserQuery
    mutation: userMutations
}

"用户变更"
type userMutations{
    "添加用户"
    addUser(addUser:UserSaveDTO):ResultModel
}

"新增用户请求对象"
input UserSaveDTO{#定义新增对象
    "用户名称"
    userName:String!
    "用户年龄"
    age:Int!
}

type ResultModel{#定义返回对象
    code:Int
    msg:String
    ecxp:String
}

type UserQuery{#定义查询类型
    getUser(userId:Long):UserVO #指定对象以及参数类型
 }

type UserVO{ #定义对象
    userId:Long! #!表示属性是非空项
    userName:String
    age:Int
    dept:DeptVO
}

type DeptVO{
    deptId:Long
    deptName:String
}

PS:定义的对象和实体类字段必须保持一样,否则在获取参数或者转换可能报错

实现根据用户id查询用户信息:

package com.fu.demo;

import com.fu.domain.vo.UserVO;
import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.Scalars;
import graphql.schema.*;

/**
 * @description:
 * @create: 2021-11-29 10:06
 **/
public class GraphQLDemo1 {
    public static void main(String[] args) {
    /* schema {
            query:UserQuery
        }
        type UserQuery{
            user:User
        }
        type User{
            id:Long!
            name:String
            age:Integer
        }
        */
        /**
         * type User{#定义对象}
         */
        GraphQLObjectType userObjectType = GraphQLObjectType.newObject()
                .name("User")
                .field(GraphQLFieldDefinition.newFieldDefinition().name("userId").type(Scalars.GraphQLLong))
                .field(GraphQLFieldDefinition.newFieldDefinition().name("userName").type(Scalars.GraphQLString))
                .field(GraphQLFieldDefinition.newFieldDefinition().name("age").type(Scalars.GraphQLInt))
                .build();

        /**
         * user:User #指定对象及查询类型
         */
        GraphQLFieldDefinition userFileldDefinition = GraphQLFieldDefinition.newFieldDefinition()
                .name("getUser")
                .type(userObjectType)
                .argument(GraphQLArgument.newArgument().name("userId").type(Scalars.GraphQLLong).build())
                //.dataFetcher(new StaticDataFetcher( new UserVO(1L,"张双",20)))
                .dataFetcher(dataFetchingEnvironment -> {
                    Long id = dataFetchingEnvironment.getArgument("userId");
                    return new UserVO(id, "张双" + id, 20);
                })
                .build();

        /**
         * type UserQuery{#定义查询类型}
         */
        GraphQLObjectType userQuery = GraphQLObjectType.newObject()
                .name("UserQuery")
                .field(userFileldDefinition)
                .build();

        /**
         * schema{#定义查询}
         */
        GraphQLSchema graphQLSchema = GraphQLSchema.newSchema().query(userQuery).build();
        GraphQL graphQL = GraphQL.newGraphQL(graphQLSchema).build();
        String query = "{getUser(userId:100){userId,userName,age}}";
        ExecutionResult execute = graphQL.execute(query);
        System.out.println(execute.toSpecification());
    }
}

执行结果:

根据用户ID查询用户的graphQL查询步骤分析:

实现新增用户:

package com.fu.demo;

import com.fu.common.domain.ResultModel;
import com.fu.domain.vo.UserVO;
import graphql.ExecutionInput;
import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.schema.GraphQLSchema;
import graphql.schema.idl.RuntimeWiring;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
import org.apache.commons.io.IOUtils;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * @description:
 * @author: wangyan
 * @create: 2021-11-30 13:52
 **/
public class GraphQLDemo3 {
    public static void main(String[] args) throws IOException {
        String fileName = "graphqls/user.graphql";
        String fileContent = IOUtils.toString(GraphQLDemo2.class.getClassLoader().getResource(fileName), "utf-8");
        TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(fileContent);
        RuntimeWiring wiring = RuntimeWiring.newRuntimeWiring().type("userMutations", builder ->
                builder.dataFetcher("addUser", dataFetchingEnvironment -> {
                    Map<String, Object> arguments = dataFetchingEnvironment.getArguments();
                    System.out.println("新增参数:" + arguments);
                    return new ResultModel(200, "新增成功", "");
                })
        ).build();
        GraphQLSchema graphQLSchema = new SchemaGenerator().makeExecutableSchema(typeRegistry, wiring);
        GraphQL graphQL = GraphQL.newGraphQL(graphQLSchema).build();
        //定义新增参数
        Map<String, Object> param = new HashMap<>();
        Map userInfo = new HashMap();
        userInfo.put("userName", "王小二");
        userInfo.put("age", 3);
        param.put("saveParam", userInfo);
        //定义graphql输入参数
        ExecutionInput executionInput = ExecutionInput.newExecutionInput()
                .variables(param)
                .query("mutation addUser($saveParam:UserSaveDTO){addUser(addUser:$saveParam){code,msg}}")
                .build();
        ExecutionResult execute = graphQL.execute(executionInput);
        System.out.println(execute.toSpecification());
    }
}

执行结果:

优化项目中GraphQL查询

SDL查询

        在“resources”目录下新建一个“graphqls”文件夹,在里面新建一个“user.graphql”文件。

(PS:idea可以安装JS Graphql插件,安装后编写文件有提示)

PS:定义的字段类型不是GraphQL标量类型需要通过“scalar”定义声明。

代码实现:

package com.fu.demo;


import com.fu.domain.vo.UserVO;
import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.schema.GraphQLSchema;
import graphql.schema.idl.RuntimeWiring;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
import org.apache.commons.io.IOUtils;

import java.io.IOException;

/**
 * @description:
 * @create: 2021-11-29 10:06
 **/
public class GraphQLDemo2 {
    public static void main(String[] args) throws IOException {

        String fileName = "graphqls/user.graphql";
        String fileContent = IOUtils.toString(GraphQLDemo2.class.getClassLoader().getResource(fileName), "utf-8");
        TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(fileContent);
        RuntimeWiring wiring = RuntimeWiring.newRuntimeWiring().type("UserQuery", builder ->
                builder.dataFetcher("getUser", dataFetchingEnvironment -> {
                    Long id = dataFetchingEnvironment.getArgument("userId");
                    return new UserVO(id, "张珊" + id, 20);
                })
        ).build();
        GraphQLSchema graphQLSchema = new SchemaGenerator().makeExecutableSchema(typeRegistry, wiring);
        GraphQL graphQL = GraphQL.newGraphQL(graphQLSchema).build();
        String query = "{getUser(userId:100){userId,userName,age}}";
        ExecutionResult execute = graphQL.execute(query);
        System.out.println(execute.toSpecification());
    }
}

        到这里graphql算入门了,从上面的demo中可以发现还可以进一步优化,下面进一步优化,通过http访问一个接口传参进行graphql查询。

http接口查询

新建GraphQLProvider类:

目的:获取GraphQL实例

package com.fu.graphql;

import graphql.GraphQL;
import graphql.schema.GraphQLSchema;
import graphql.schema.idl.RuntimeWiring;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.util.ResourceUtils;

import javax.annotation.PostConstruct;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.List;

/**
 * @description:
 * @create: 2021-11-29 13:28
 **/
@Component
public class GraphQLProvider {
    private GraphQL graphQL;
    @Autowired
    private List<MyDataFetcher> myDataFetcherList;

    @PostConstruct
    public void init() throws FileNotFoundException {
        File file = ResourceUtils.getFile("classpath:graphqls/user.graphql");
        this.graphQL = GraphQL.newGraphQL(buidlGraphQLSchema(file)).build();
    }

    @Bean
    public GraphQL graphQL() {
        return graphQL;
    }

    private GraphQLSchema buidlGraphQLSchema(File file) {
        TypeDefinitionRegistry registry = new SchemaParser().parse(file);
        return new SchemaGenerator().makeExecutableSchema(registry, buidRuntimeWiring());
    }

    private RuntimeWiring buidRuntimeWiring() {
        return RuntimeWiring.newRuntimeWiring().type("UserQuery", builder -> {
                    for (MyDataFetcher myDataFetcher : myDataFetcherList) {
                        builder.dataFetcher(myDataFetcher.fieldName(), dataFetchingEnvironment -> myDataFetcher.dataFetcher(dataFetchingEnvironment));
                    }
                    return builder;
                }
        ).build();
    }
}

新建MyDataFetcher接口:

目的:每个API接口获取方式都不一样,因此自定义一个获取数据接口。

package com.fu.graphql;

import graphql.schema.DataFetchingEnvironment;

/**
 * @description: 获取数据
 * @create: 2021-11-29 13:17
 **/
public interface MyDataFetcher {
    /**graphQL查询的名称*/
    String fieldName();

    /**接口数据查询*/
    Object dataFetcher(DataFetchingEnvironment environment);
}

新建UserController:

package com.fu.domain.controller;

import graphql.ExecutionResult;
import graphql.GraphQL;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

/**
 * @description:
 * @create: 2021-11-29 15:57
 **/
@RestController
@RequestMapping
public class UserController {
    @Autowired
    private GraphQL graphQL;

    @GetMapping("/graphql")
    public Map<String, Object> graphql(String query) {
        ExecutionResult execute = graphQL.execute(query);
        System.out.println(execute.toSpecification());
        return execute.toSpecification();
    }
}

项目启动测试

安装google插件:

PS:可以安装谷歌“altair graphql client”插件,安装后,通过这个工具写sdl时会有检验和提示。

实例-查询用户id=1的用户:

实例-查询用户id=1,2的用户:

PS:这里需要用到别名,返回对象一样的情况下,需要用别名,否则报错

实例-多接口合并查询:查询用户id=1,2的用户和用户id=1的角色信息(listRole是一个接口

(推荐)graphql-java-kickstart实现graphQL

项目基架:springboot2.6

官网地址:

About GraphQL Spring Boot - GraphQL Java Kickstart

与RESTful实现差异对比

        从图可以看出,接收前端请求分发处理这一层有差异,而业务层和持久层都是无差异的。因此用springboot 实现graphql API就是把controller改造成resolver。

引入graph maven依赖

参考官网地址:Getting started - GraphQL Java Kickstart

<dependency>
    <groupId>com.graphql-java-kickstart</groupId>
    <artifactId>graphql-spring-boot-starter</artifactId>
    <version>11.0.0</version>
</dependency>

<!--自定义字段类型(因为graph标量类型在大多数情况下都不满足项目需要,
     比如Date类型,这个就需要自行定义)
-->
<!--官网地址:https://github.com/graphql-java/graphql-java-extended-scalars-->
<dependency>
    <groupId>com.graphql-java</groupId>
    <artifactId>graphql-java-extended-scalars</artifactId>
    <version>16.0.0</version>
</dependency>

注:如果jar下载不下来,可以配置三方仓库,这个请参照官网。

项目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>2.6.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.fu</groupId>
    <artifactId>springboot-graphql-server-kick</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-graphql-server-kick</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.graphql-java-kickstart</groupId>
            <artifactId>graphql-spring-boot-starter</artifactId>
            <version>11.0.0</version>
        </dependency>
       <dependency>
            <groupId>com.graphql-java</groupId>
            <artifactId>graphql-java-extended-scalars</artifactId>
            <version>16.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.8</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.27</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>
        <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper-spring-boot-starter</artifactId>
        <version>1.4.1</version>
    </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

编写第一个demo程序

定义查询schame结构

#声明自定义数据类型
scalar Long
schema {#定义查询
    query: UserQuery
    mutation: UserMutaions
}

"用户查询"
type UserQuery{#定义查询类型
    #getUser要和编写的resolver中的方法名保持一致,参数名可以不一样,只要类型一致即可,否则调用报错
    "根据id查询用户"
    getUser(userId:Long):SysUserVO 
    "列表分页查询用户"
    listUser(pageNum:Int,pageSize:Int):ListResultModel
}

"用户变更"
type UserMutaions{#定义增删改类型
    "新增用户"
    addUser(addUser:SysUserSaveDTO):ResultModel
    "修改用户"
    updateUser(updateUser:SysUserSaveDTO):ResultModel
}

"用户返回对象"
type SysUserVO{#定义返回对象
    "用户id"
    userId:Long
    "用户名称"
    userName:String
    "用户账号"
    nickName:String
    "用户性别"
    sex:String
}

"列表查询返回对象"
type ListResultModel{#定义返回对象
    "编码:200-成功"
    code:Int
    "提示信息"
    msg:String
    "列表数据"
    rows:[SysUserVO]
    "总条数"
    total:Int
    "总页数"
    pages:Int
    "当前页"
    currentPage:Int
}

"变更返回对象"
type ResultModel{#定义返回对象
     "编码:200-成功"
    code:Int
    "提示信息"
    msg:String
    "异常信息"
    excp:String
}

"新增修改请求对象"
input SysUserSaveDTO{#定义新增对象
   "用户id"
    userId:Long
    "用户名称"
    userName:String
    "用户账号"
    nickName:String
    "用户密码"
    password:String
    "用户性别"
    sex:String
}

PS:定义的对象和实体类字段必须保持一样,否则在获取参数或者转换可能报错

实现用户查询resolver:

新建的类实现GraphQLQueryResolver接口即可实现graphql查询。

package com.fu.resolver;

import com.fu.common.domain.ListResultModel;
import com.fu.domain.entity.SysUser;
import com.fu.domain.vo.SysUserVO;
import com.fu.service.ISysUserService;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import graphql.kickstart.tools.GraphQLQueryResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

/**
 * @description: 用户查询解析器
 * @create: 2021-11-30 15:13
 **/
@Component
public class UserQueryResolver implements GraphQLQueryResolver {
    protected final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Autowired
    private ISysUserService sysUserService;

    /**根据查询用户*/
    public SysUserVO getUser(Long userId) {
        SysUser user = sysUserService.getUser(userId);
        return new SysUserVO(user);
    }

    /**列表查询用户*/
    public ListResultModel<SysUserVO> listUser(Integer pageNum, Integer pageSize) {
        ListResultModel<SysUserVO> result = new ListResultModel<>();
        try {
            PageHelper.startPage(pageNum, pageSize);
            List<SysUser> dataList = sysUserService.listUser(new SysUser());
            if (dataList != null && dataList.size() > 0) {
                List<SysUserVO> datas = new ArrayList<>();
                dataList.forEach(v -> datas.add(new SysUserVO(v)));
                PageInfo pageInfo = new PageInfo(dataList);
                result = new ListResultModel(pageInfo, datas);
            }
        } catch (Exception e) {
            ListResultModel.getErrorResult(result, e);
            logger.error("UserQueryResolver-listUser-error", e);
        }
        return result;
    }
}

实现用户增删改resolver:

新建的类实现GraphQLMutationResolver接口即可。

package com.fu.resolver;

import com.fu.common.domain.ResultModel;
import com.fu.domain.dto.SysUserSaveDTO;
import com.fu.domain.entity.SysUser;
import com.fu.service.ISysUserService;
import graphql.kickstart.tools.GraphQLMutationResolver;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @description: 用户增删改查分析器
 * @create: 2021-11-30 15:19
 **/
@Component
public class UserMutationResolver implements GraphQLMutationResolver {
    @Autowired
    private ISysUserService sysUserService;

    /**新增用户*/
    public ResultModel addUser(SysUserSaveDTO dto) {
        System.out.println("新增参数:" + dto.toString());
        SysUser user = new SysUser();
        BeanUtils.copyProperties(dto, user);
        sysUserService.saveUser(user);
        return new ResultModel(200, "新增成功", "");
    }

    /**修改用户*/
    public ResultModel updateUser(SysUserSaveDTO dto) {
        System.out.println("修改参数:" + dto.toString());
        if (dto.getUserId() == null) {
            return new ResultModel(200, "修改失败:用户id为空", "");
        }
        SysUser user = new SysUser();
        BeanUtils.copyProperties(dto, user);
        sysUserService.saveUser(user);
        return new ResultModel(200, "修改成功", "");
    }
}

PS:其实可以查询和增删改resolver写成一个也是没有问题的,但是graphql规范查询和变更定义是不一样的,所以为了规范写了两个。

底层实现就不贴代码了,有兴趣的小伙伴请移驾

空白/springboot-graphql - Gitee.com

启动项目测试

查看接口文档

根据id查询用户

分页列表查询用户

新增用户

再次查询看添加数据是否有了:

修改用户

再次查询看数据是否修改了:

参考网址

GraphQL官网:GraphQL | A query language for your API

博客:Springboot中集成GraphQL_chen_duochuang的博客-CSDN博客

官网地址:About GraphQL Spring Boot - GraphQL Java Kickstart

自定义scalar:Spring Boot GraphQL 实战 02_增删改查和自定义标量 - Coder小黑 - 博客园

扩展scalar官网地址:https://github.com/graphql-java/graphql-java-extended-scalars

项目中遇到的问题

graphql增删改跨域问题

springboot2.6pagehelper循环依赖

现象描述:springboot2.6.0 pagehelper-spring-boot-starter PageHelperAutoConfiguration报循环依赖错误

解决办法:

第一种:网上百度的都说,在SpringBoot Application启动类中“@SpringBootApplication”添加extend如下

@SpringBootApplication(exclude = {PageHelperAutoConfiguration.class})。这种方式让项目正常运行了,但是分页也失效了。

第二种:降低springBoot版本,我降到2.5.6然后启动ok,分页ok

第三种(推荐):升级pagehelper-spring-boot-starter 版本到1.4.1,这个还是摸到github官网上看到更新日志的,如下

项目启动正常运行,分页ok

总结

API 的查询语言。

和RESTful核心差异资源的描述信息与其获取方式相分离。

RESTful服务端决定返回结果,GraphQL客户端决定返回结果。

RESTful和GraphQL都是返回json。

Logo

Authing 是一款以开发者为中心的全场景身份云产品,集成了所有主流身份认证协议,为企业和开发者提供完善安全的用户认证和访问管理服务

更多推荐