SpringBoot + GraphQL + Mysql 实现 CRUD

1,026 阅读4分钟

SpringBoot + GraphQL + Mysql 实现 CRUD

GraphQL是一种查询语言用来设计 APIs,可替代 REST,SOAP 或 gRPC,详细的可以查看官网解释: GraphQL

本教程中,我们将构建一个 Spring Boot GraphQL 例子并提供 CRUD APIs 来从 mysql 数据库中创建,查询,更新,删除对象。

Sring Boot CRUD GraphQL APIs 概览

目标

我们有两个数据模型:作者(Author) 和 课程(Tutorial)。

Author {
  id: Long
  name: String
  age: Integer
}

Tutorial {
  id: Long
  title: String
  description: String
  author: Author
}

一个作者可能有多个课程 , 所以他们是一对多的关系

CRUD GraphQL APIs

创建作者:

GraphQL

mutation {
  createAuthor(
    name: "bezkoder",
    age: 27) {
      id 
      name
  }
}

响应

{
  "data": {
    "createAuthor": {
      "id": "1",
      "name": "bezkoder"
    }
  }
}

查询所有作者

GraphQL

query {
  findAllAuthors{
    id
    name
    age
  }
}

响应

{
  "data": {
    "findAllAuthors": [
      {
        "id": 1,
        "name": "张三",
        "age": 25
      },
      {
        "id": 2,
        "name": "李四"
        "age": 43
      }
    ]
  }
}

等等,这里先不一一列举了

如果查看数据库,你会发现有两张表: author 和 tutorial

内容如下

mysql> select * from author;
+----+-----+--------+
| id | age | name   |
+----+-----+--------+
|  1 |  25 | 张三   |
|  2 |  43 | 李四   |
+----+-----+--------+

mysql> select * from tutorial;
+----+-------------------------------------+----------------+-----------+
| id | description                         | title          | author_id |
+----+-------------------------------------+----------------+-----------+
|  1 | 该教程可以帮助你学习 Java              | 学习 Java       | 1         |
|  2 | 很棒的 GraphQL 教程                  | 学习 GraphQL    | 2         |
+----+-------------------------------------+----------------+-----------+

开始构建APIs

用到的依赖

我们的应用将用到下面这些:

  • Java8 或者 java11
  • Spring Boot 2.2.1.RELEASE (with Spring Web)
  • Graphql-spring-boot-starter 7.0.1
  • Graphiql-spring-boot-starter 7.0.1
  • Gradle 6.3
  • MySQL 5.7

项目结构

image.png

  • resources/graphql 中所有的 .graphqls 定义了 GraphQL schemas
  • model: 存放 Author 和 Tutorial 实体类
  • mapper: 存放 Mapper 接口来和 MySQL 交互
  • resolver: 通过实现一些 Resolver接口来处理查询和修改的请求,该类可以理解成 SpringMVC 中的 Controller 层
  • application.yml: 配置文件
  • build.gradle: gradle 的构建文件

项目准备

首先创建 Spring Boot 项目,然后添加依赖

dependencies {
  // SpringBoot
  implementation 'org.springframework.boot:spring-boot-starter-web'
  developmentOnly 'org.springframework.boot:spring-boot-devtools'
  testImplementation('org.springframework.boot:spring-boot-starter-test') {
    exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
  }

  // Graphql
  implementation 'com.graphql-java-kickstart:graphql-spring-boot-starter:7.0.1'
  implementation 'com.graphql-java-kickstart:graphiql-spring-boot-starter:7.0.1'

  // Mybatis-Plus
  implementation 'com.baomidou:mybatis-plus-boot-starter:3.1.2'

  // Mysql
  implementation 'mysql:mysql-connector-java:5.1.6'

  // Flyway
  implementation 'org.flywaydb:flyway-core:6.3.3'

  // Lombok
  compileOnly 'org.projectlombok:lombok'
  annotationProcessor 'org.projectlombok:lombok'

  // Spock
  testImplementation 'org.spockframework:spock-core:2.0-M1-groovy-2.5'
  testImplementation 'org.spockframework:spock-spring:2.0-M1-groovy-2.5'
}

相关配置文件

打开 application.yml 并添加下面的配置

server:
  port: 8083

spring:
  flyway:
    locations: classpath:db/migration
    out-of-order: true
    url: ${spring.datasource.url}
    user: ${spring.datasource.username}
    password: ${spring.datasource.password}
  datasource:
    url: jdbc:mysql://localhost:3306/learn_graphql
    username: root
    password:
    driver-class-name: com.mysql.jdbc.Driver

mybatis-plus:
  type-aliases-package: com.kba977.learngraphql.mapper

创建 GraphQL Schema

我们为了将schema拆分到不同的文件里,以分类管理,Spring Boot GraphQL 会自动扫描所有 schema 文件

首先我们创建 Schemas

author.graphqls

type Author {
    id: ID!
    name: String!
    age: Int
}

input CreateAuthorInput {
    name: String!
    age: Int!
}

tutorial.graphqls

type Tutorial {
    id: ID!
    title: String!
    description: String
    author: Author
}

input CreateTutorialInput {
    title: String!
    description: String!
    authorId: ID!
}

input UpdateTutorialInput {
    title: String
    description: String
}

query.graphqls

# Root

type Query {
    # Author
    findAllAuthors: [Author]!
    findAuthorById(id: ID!): Author
    countAuthors: Int!

    # Tutorial
    findAllTutorials: [Tutorial]!
    findTutorialById(id: ID!): Tutorial
    countTutorials: Int!

}

mutation.graphqls

# Root

type Mutation {
    # Author
    createAuthor(createAuthorInput: CreateAuthorInput!): Author!

    # Tutorial
    createTutorial(createTutorialInput: CreateTutorialInput!): Tutorial!
    updateTutorial(id: ID!, updateTutorialInput: UpdateTutorialInput): Tutorial!
    deleteTutorial(id: ID!): Boolean

}

在 GraphQL 中分为两个大类,所有的查询入口在 Query, 所有的变更入口都在 Mutation

上面文件中 ! 的意思该字段不能为 null,如果加 ! ,那么在返回值的时候 GraphQL允许返回 null

定义实体类,Mapper接口,Service实现

这些我们比较熟悉,直接跳过大家可以查看源码

实现GraphQL Root 解析器

如下面文件 TutorialMutationResolver 实现了对 Tutorial 的变更请求处理

package com.kba977.learngraphql.graphql.resolver.mutation;

import com.kba977.learngraphql.graphql.type.tutorial.CreateTutorialInput;
import com.kba977.learngraphql.graphql.type.tutorial.UpdateTutorialInput;
import com.kba977.learngraphql.model.Tutorial;
import com.kba977.learngraphql.service.TutorialService;
import graphql.kickstart.tools.GraphQLMutationResolver;
import org.apache.ibatis.javassist.NotFoundException;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Optional;

@Component
public class TutorialMutationResolver implements GraphQLMutationResolver {

    @Autowired
    private TutorialService tutorialService;
    
    public Tutorial createTutorial(CreateTutorialInput input) {
        Tutorial tutorial = new Tutorial(input.getTitle(), input.getDescription(), input.getAuthorId());
        tutorialService.save(tutorial);
        return tutorial;
    }
    
    public Tutorial updateTutorial(Integer id, UpdateTutorialInput input) throws NotFoundException {
        Optional<Tutorial> optTutorial = Optional.ofNullable(tutorialService.getById(id));
    
        if (optTutorial.isPresent()) {
            Tutorial tutorial = optTutorial.get();
            BeanUtils.copyProperties(input, tutorial);
            tutorialService.updateById(tutorial);
    
            return tutorialService.getById(id);
        }
        throw new NotFoundException("Not found Tutorial to update!");
     }
    
    public boolean deleteTutorial(Integer id) {
        return tutorialService.removeById(id);
    }

}

实现 GraphQL 字段解析器

对于 Tutorial 中的复杂类型 author,我们需要字段解析器去解析它的值,TutorialResolver 实现了 GraphQLResolver 接口并实现 getAuthor() 方法

resolver/TutorialResolver.java

package com.kba977.learngraphql.graphql.resolver;

import com.kba977.learngraphql.model.Author;
import com.kba977.learngraphql.model.Tutorial;
import com.kba977.learngraphql.service.AuthorService;
import graphql.kickstart.tools.GraphQLResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Optional;

@Component
public class TutorialResolver implements GraphQLResolver<Tutorial> {

    @Autowired
    private AuthorService authorService;
    
    public Author getAuthor(Tutorial tutorial) {
        return authorService.getById(tutorial.getAuthorId());
    }

}

如果客户端请求 Tutorial 没有带上 author 字段,那么 GraphQL Server 将不会调用该方法去解析字段

运行应用并检查结果

应用启动后,打开浏览器访问 localhost:8083/graphiql

fragment author on Author {
  id
  name
  age
}

query findAllAuthors {
  findAllAuthors {
    ...author
  }
}

query findAuthorById {
  findAuthorById(id: 3) {
    ...author
  }
}

query countAuthors {
  countAuthors 
}

fragment tutorial on Tutorial {
  id
  title
  description
  author {
    ...author
  }
}

query findAllTutorials {
  findAllTutorials {
    ...tutorial
  }
}

query countTutorials {
  countTutorials 
}

mutation createAuthor {
  createAuthor(createAuthorInput: {
    name: "kba999"
    age: 20 }) 
  {
    ...author
  }
}

mutation createTutorial {
  createTutorial(createTutorialInput: {
    title: "新概念英语"
    description: "学习英语的好资料"
    authorId: 2
  }) {
    ...tutorial
  }
}

mutation updateTutorial {
  updateTutorial(id: 4, updateTutorialInput: {
    title: "我们的世界"
  }) {
    ...tutorial
  }
}

mutation deleteTutorial {
  deleteTutorial(id: 1)
}

image.png

进一步学习资源

GraphQL documentation www.graphql-java.com/tutorials/g… graphql-java Github 源代码 你可以从github上找到完整的代码:github.com/kba977/lear…