项目学习GraphQL(四)

4. GraphQL Server实现——基于SpringBoot+GraphQL

在实现一个GraphQL Server之前,建议大家先去看看一个具体的GraphQL查询语句是如何执行的,由于理论的内容太多,请大家具体参阅:
https://graphql.org/learn/execution/
本章节项目案例及部分代码参考自:
https://www.graphql-java.com/documentation/v14/

4.1 SpringBoot简介

SpringBoot应该是目前基于Java语言的最重要的一个框架了,也是Java开源框架中的一个具有划时代意义的产品。结合Java面向对象的语言特性和Spring的AOP软件工程思想,应该说SpringBoot是开发大型、复杂的前后端分离的软件的不二选择。特别是由Java衍生出的Scala结合Spark、Hadoop等大数据平台,为打造能力更强的“大后端”提供了可能性。
下面我们就结合一个具体的例子,来看看如何使用SpringBoot来开发一个我们自己的GraphQL Server。

4.2 开发平台和开发工具

Java开发工具:IntellJ IDEA,一个能够让我这个用了十多年Eclipse的转投IntellJ自然尤其自身的优势。
数据库:MySQL
数据库管理工具:MySQL Workbench

4.3 具体开发步骤

4.3.1 MySQL数据库创建

CREATE DATABASE /*!32312 IF NOT EXISTS*/`graphql` /*!40100 DEFAULT CHARACTER SET utf8 */;
 
USE `graphql`;
 
/*Table structure for table `author` */
 
DROP TABLE IF EXISTS `author`;
 
CREATE TABLE `author` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'Key word',
  `created_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Created time',
  `updated_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Updated time',
  `first_name` varchar(50) DEFAULT NULL COMMENT 'firstName',
  `last_name` varchar(50) DEFAULT NULL COMMENT 'lastName',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
 
/*Table structure for table `book` */
 
DROP TABLE IF EXISTS `book`;
 
CREATE TABLE `book` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'Key word',
  `created_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Created time',
  `updated_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Updated time',
  `title` varchar(50) DEFAULT NULL COMMENT 'Title',
  `author_id` bigint(20) NOT NULL,
  `isbn` varchar(255) DEFAULT NULL,
  `page_count` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
 
/*Table structure for table `user` */
 
DROP TABLE IF EXISTS `user`;

执行完上述代码之后,在MySQL中能创建一个名为graphql的database。它包含两个表格“author”和“book”。
创建完成之后,schema的结构如下:
项目学习GraphQL(四)_第1张图片

4.3.2 在IntellJ中创建SpringBoot项目

在IntellJ中,选择File=>new=>project,在new project对话框中,选择“Spring Initia”,在“Project SDK”中建议选择“1.8”,因为本人在实际开发过程中SDK 11曾经出现过很多兼容性问题。
项目学习GraphQL(四)_第2张图片
单击“Next”
项目学习GraphQL(四)_第3张图片
在这一步就按默认的选项,Java version建议选“8”,单击“Next”
在这一步我们需要选择项目当中需要用的的dependecies:
项目学习GraphQL(四)_第4张图片
在本项目中,需要用到以下dependencies:

  1. Developer Tools在这里插入图片描述

  2. Web在这里插入图片描述

  3. Template Engine在这里插入图片描述

  4. SQL.项目学习GraphQL(四)_第5张图片在这里我们要同时添加 Spring Data JPA和MySQL Driver,分别用于创建数据中间层和MySQL的驱动

  5. 最后为项目命名并选择存储位置项目学习GraphQL(四)_第6张图片

4.3.3 在IntellJ中添加GraphQL plugin

File=>setting,在setting对话框中选择plugins,确认添加了JS GraphQL plugin
项目学习GraphQL(四)_第7张图片

4.3.4 添加GraphQL的dependencies

目前在SpringBoot中对GraphQL支持比较好的Package就是graphql-java-kickstart了,它集成了相当多的有用工具,比原生的GraphQL-Java package要好用很多,而且在其中就集成了GraphQL Playground调试工具,测试GraphQL语句非常方便。详细请参阅:https://github.com/graphql-java-kickstart/graphql-spring-boot
添加完上述所需dependencies后,完整的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>2.4.0-SNAPSHOTversion>
		<relativePath/> 
	parent>
	<groupId>com.examplegroupId>
	<artifactId>springboot_graphqlartifactId>
	<version>0.0.1-SNAPSHOTversion>
	<name>springboot_graphqlname>
	<description>Demo project for Spring Bootdescription>

	<properties>
		<java.version>8java.version>
		<kotlin.version>0.3.70kotlin.version>
	properties>


	<dependencies>
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-data-jpaartifactId>
		dependency>
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-webartifactId>
		dependency>

		<dependency>
			<groupId>mysqlgroupId>
			<artifactId>mysql-connector-javaartifactId>
			<scope>runtimescope>
		dependency>
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-testartifactId>
			<scope>testscope>
		dependency>

		
		<dependency>
			<groupId>com.zaxxergroupId>
			<artifactId>HikariCPartifactId>
			<version>3.4.5version>
		dependency>

		

		<dependency>
			<groupId>com.graphql-java-kickstartgroupId>
			<artifactId>graphql-spring-boot-starterartifactId>
			<version>8.0.0version>
		dependency>

		
		<dependency>
			<groupId>com.graphql-java-kickstartgroupId>
			<artifactId>altair-spring-boot-starterartifactId>
			<version>8.0.0version>
			<scope>runtimescope>
		dependency>

		
		<dependency>
			<groupId>com.graphql-java-kickstartgroupId>
			<artifactId>graphiql-spring-boot-starterartifactId>
			<version>8.0.0version>
			<scope>runtimescope>
		dependency>

		
		<dependency>
			<groupId>com.graphql-java-kickstartgroupId>
			<artifactId>voyager-spring-boot-starterartifactId>
			<version>8.0.0version>
			<scope>runtimescope>
		dependency>

		
		<dependency>
			<groupId>com.graphql-java-kickstartgroupId>
			<artifactId>graphql-spring-boot-starter-testartifactId>
			<version>8.0.0version>
			<scope>testscope>
		dependency>

		
		<dependency>
			<groupId>com.graphql-java-kickstartgroupId>
			<artifactId>playground-spring-boot-starterartifactId>
			<version>8.0.0version>
			<scope>runtimescope>
		dependency>

		<dependency>
			<groupId>io.github.graphql-javagroupId>
			<artifactId>graphql-java-annotationsartifactId>
			<version>8.2version>
		dependency>


		


		
		<dependency>
			<groupId>org.projectlombokgroupId>
			<artifactId>lombokartifactId>
			<version>1.18.14version>
			<scope>providedscope>
		dependency>

		
		<dependency>
			<groupId>com.coxautodevgroupId>
			<artifactId>graphql-java-toolsartifactId>
			<version>2.1.2version>
		dependency>
	dependencies>

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

	<repositories>
		<repository>
			<id>spring-milestonesid>
			<name>Spring Milestonesname>
			<url>https://repo.spring.io/milestoneurl>
		repository>
		<repository>
			<id>spring-snapshotsid>
			<name>Spring Snapshotsname>
			<url>https://repo.spring.io/snapshoturl>
			<snapshots>
				<enabled>trueenabled>
			snapshots>
		repository>

		<repository>
			<id>jcenterid>
			<url>https://jcenter.bintray.com/url>
		repository>

	repositories>
	<pluginRepositories>
		<pluginRepository>
			<id>spring-milestonesid>
			<name>Spring Milestonesname>
			<url>https://repo.spring.io/milestoneurl>
		pluginRepository>
		<pluginRepository>
			<id>spring-snapshotsid>
			<name>Spring Snapshotsname>
			<url>https://repo.spring.io/snapshoturl>
			<snapshots>
				<enabled>trueenabled>
			snapshots>
		pluginRepository>
	pluginRepositories>

project>

4.3.5 创建项目目录

本项目目录结构如下:
项目学习GraphQL(四)_第8张图片

  • config目录:存放config配置文件 entity目录:存放entity实体类文件,entity实体类与MySQL数据库中表

  • model目录:存放数据model文件 repo目录:repository目录,存放对数据模型操作文件

  • resolver目录:这是GraphQL

  • server项目中最为重要的目录,里面的文件负责将前端的GraphQL操作转换成真实的、基于SpringBoot的数据库操作。

同时,在项目的resources目录中创建graphql目录,用于存放与GraphQL相关的.graphql文件:
项目学习GraphQL(四)_第9张图片

4.4.4 application.yml项目配置文件

当成功创建一个SpringBoot项目时,系统会自动为我们创建一个application.properties文件,主要用于系统运行时用到的环境参数变量以及相关的值,在这里我们用自己定义的application.yml文件,相比较.properties文件,.yml文件的层次更加清晰也更简洁,其内容如下:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/graphql?serverTimezone=UTC
    username: root
    password: 1234
#    driver-class-name: com.mysql.jdbc.Driver
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      minimum-idle: 10
      maximum-pool-size: 20
      idle-timeout: 60000
      max-lifetime: 1800000
      connection-timeout: 30000
      data-source-properties:
        cachePreStmts: true
        preStmtCacheSize: 250


  main:
    allow-bean-definition-overriding: true
#    dbcp2:
#      driver-class-name: com.mysql.jdbc.Driver
#      max-wait-millis: 20
#      max-idle: 8
#      min-idle: 8
#      initial-size: 10
  jpa:
    hibernate:
      ddl-auto: update
      database: mysql


graphql:
  servlet:
    mapping: /graphql
    enabled: true
    corsEnabled: true
    cors:
      allowed-origins: http://some.domain.com
    # if you want to @ExceptionHandler annotation for custom GraphQLErrors
    exception-handlers-enabled: true
    contextSetting: PER_REQUEST_WITH_INSTRUMENTATION
  tools:
    schema-location-pattern: "**/*.graphql"
    # Enable or disable the introspection query. Disabling it puts your server in contravention of the GraphQL
    # specification and expectations of most clients, so use this option with caution
    introspection-enabled: true
  annotations:
    base-package: com.exaple.springboot_graphql # required
    always-prettify: true #true is the default value, no need to specify it
graphql.playground:
  mapping: /playground
  endpoint: /graphql
  subscriptionEndpoint: /subscriptions
  staticPath.base: my-playground-resources-folder
  enabled: true
  pageTitle: Playground
  cdn:
    enabled: false
    version: latest
  settings:
    editor.cursorShape: line
    editor.fontFamily: "'Source Code Pro', 'Consolas', 'Inconsolata', 'Droid Sans Mono', 'Monaco', monospace"
    editor.fontSize: 14
    editor.reuseHeaders: true
    editor.theme: dark
    general.betaUpdates: false
    prettier.printWidth: 80
    prettier.tabWidth: 2
    prettier.useTabs: false
    request.credentials: omit
    schema.polling.enable: true
    schema.polling.endpointFilter: "*localhost*"
    schema.polling.interval: 2000
    schema.disableComments: true
    tracing.hideTracingResponse: true
  headers:
    headerFor: AllTabs
  tabs:
    - name: Example Tab
      query: classpath:exampleQuery.graphql
      headers:
        SomeHeader: Some value
      variables: classpath:variables.json
      responses:
        - classpath:exampleResponse1.json
        - classpath:exampleResponse2.json

在文件中比较重要的配置内容有:

  • datasource: 配置数据库连接参数;

  • hikari: 配置数据库连接池,使用数据连接池可以大大优化对数据库的连接域访问速度,具体请参阅:https://www.baeldung.com/spring-boot-hikari

  • graphql:graphql的基本配置;

  • graphql.playground: playground调试工具的基本配置

4.4.5 entity目录中的文件

entity目录中的文件为数据实体文件,该目录中的文件与数据库中的表对应:
在这里插入图片描述
在数据库中我们创建了author表和book表,因此我们就创建了Author.java和Book.java文件与之相对应。并且在author表和book表中分别都有created_time、updated_time以及Id字段,为避免冗余,我们创建了BaseEntity.java文件来对应这些字段。
各文件内容代码如下:

  1. BaseEntity.java
package com.exaple.springboot_graphql.entity;

import lombok.Data;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;

/**
 * @author comqiao
 * @create 2020-11-12 17:18
 */
@Data
@MappedSuperclass
public class BaseEntity implements Serializable {


    /** ID */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(columnDefinition = "bigint", nullable = false)
    protected Long id;

    /** Create Timestamp (unit:second) */
    @Column(nullable = false, updatable = false)
    @Temporal(TemporalType.TIMESTAMP)
    protected Date createdTime;

    /** Update Timestamp (unit:second) */
    @Column(nullable = false)
    @Temporal(TemporalType.TIMESTAMP)
    protected Date updatedTime;


    public BaseEntity() {
        createdTime = new Date();
        updatedTime = createdTime;
    }

    @PreUpdate
    private void doPreUpdate() {
        updatedTime = new Date();
    }

}
  1. Author.java
package com.exaple.springboot_graphql.entity;

import lombok.Data;
import lombok.EqualsAndHashCode;

import javax.persistence.Column;
import javax.persistence.Entity;


@Entity
@Data
@EqualsAndHashCode(callSuper = false)
public class Author extends BaseEntity {

    @Column(columnDefinition = "varchar(50)")
    private String firstName;

    @Column(columnDefinition = "varchar(50)")
    private String lastName;
}
  1. Book.java
package com.exaple.springboot_graphql.entity;


import lombok.Data;
import lombok.EqualsAndHashCode;

import javax.persistence.Column;
import javax.persistence.Entity;


@Entity
@Data
@EqualsAndHashCode(callSuper = false)
public class Book extends BaseEntity {
    @Column(columnDefinition = "varchar(50)")
    private String title;

    private String isbn;

    private int pageCount;

    private long authorId;
}

4.4.6 model目录中的文件

这个目录中的数据model文件要与GraphQL中schema中所定义的数据操作相关联,例如我在schema中定义了一个type为input,名称为BookInput的model(Type definition的内容请查阅前面章节)
项目学习GraphQL(四)_第10张图片
因此,在这个目录中,我们需要编写一个BookInput.java文件与之相对应,内容如下:

package com.exaple.springboot_graphql.model;


import lombok.Data;


@Data
public class BookInput {
    private String title;

    private String isbn;

    private int pageCount;

    private long authorId;
}

4.4.7 repo目录中的文件

repo目录中的文件都是Interface文件,它们均继承自JpaRepository接口。在这些接口文件中定义了数据访问及操作接口。具体文件内容如下:

  1. AuthorRepo.java
package com.exaple.springboot_graphql.repo;

import com.exaple.springboot_graphql.entity.Author;
import org.springframework.data.jpa.repository.JpaRepository;


/**
 * @author comqiao
 * @create 10-10-2020 13:35
 */
public interface AuthorRepo extends JpaRepository<Author,Long> {

    Author findAuthorById(Long id);
}
  1. BookRepo.java
package com.exaple.springboot_graphql.repo;

import com.exaple.springboot_graphql.entity.Book;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

/**
 * @author comqiao
 * @create 2020-10-10 13:39
 */
public interface BookRepo extends JpaRepository<Book,Long> {
    List<Book> findByAuthorId(Long id);

    Book findBookById(Long id);
}

4.4.8 resolver目录中的文件

这个目录中的文件负责实现对数据库操作的具体实现,与以往的SpringBoot文件不同的是,这个目录中的问价还要负责将前端传递过来的GraphQL语句解析成具体的数据操作代码,因此该目录中有以下文件:

  1. AuthorResolver.java
package com.exaple.springboot_graphql.resolver;

import com.exaple.springboot_graphql.entity.Author;
import com.exaple.springboot_graphql.entity.Book;
import graphql.kickstart.tools.GraphQLResolver;

import com.exaple.springboot_graphql.repo.BookRepo;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.List;

/**
 * @author comqiao
 * @create 2020-10-12 12:03
 */
@Component
@AllArgsConstructor
public class AuthorResolver implements GraphQLResolver<Author> {
    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    private BookRepo bookRepo;

    public String getCreatedTime(Author author) {
        return sdf.format(author.getCreatedTime());
    }

    public List<Book> getBooks(Author author) {
        return bookRepo.findByAuthorId(author.getId());
    }


}

  1. BookResolver.java
package com.exaple.springboot_graphql.resolver;

import graphql.kickstart.tools.GraphQLResolver;
import com.exaple.springboot_graphql.entity.Author;
import com.exaple.springboot_graphql.entity.Book;
import com.exaple.springboot_graphql.repo.AuthorRepo;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;

/**
 * @author comqiao
 * @create 2020-10-12 12:10
 */
@Component
@AllArgsConstructor
public class BookResolver implements GraphQLResolver<Book> {
    private AuthorRepo authorRepo;

    public Author getAuthor(Book book) {
        return authorRepo.findAuthorById(book.getAuthorId());
    }

}
  1. Mutation.java
package com.exaple.springboot_graphql.resolver;

import graphql.kickstart.tools.GraphQLMutationResolver;
import com.exaple.springboot_graphql.entity.Author;
import com.exaple.springboot_graphql.entity.Book;
import com.exaple.springboot_graphql.model.BookInput;
import com.exaple.springboot_graphql.repo.AuthorRepo;
import com.exaple.springboot_graphql.repo.BookRepo;

import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;

/**
 * @author comqiao
 * @create 2019-10-14 12:46
 */
@Component
@AllArgsConstructor
public class Mutation implements GraphQLMutationResolver {
    private AuthorRepo authorRepo;
    private BookRepo bookRepo;

    public Author newAuthor(String firstName, String lastName) {
        Author author = new Author();
        author.setFirstName(firstName);
        author.setLastName(lastName);
        return authorRepo.save(author);
    }

    public Book newBook(String title, String isbn, int pageCount, Long authorId) {
        Book book = new Book();
        book.setTitle(title);
        book.setIsbn(isbn);
        book.setPageCount(pageCount);
        book.setAuthorId(authorId);
        return bookRepo.save(book);
    }


    public Book saveBook(BookInput input) {
        Book book = new Book();
        book.setTitle(input.getTitle());
        book.setIsbn(input.getIsbn());
        book.setPageCount(input.getPageCount());
        book.setAuthorId(input.getAuthorId());
        return bookRepo.save(book);
    }

    public Boolean deleteBook(Long id) {
        bookRepo.deleteById(id);
        return true;
    }

    public Book updateBookPageCount(int pageCount,long id) {
        Book book = bookRepo.findBookById(id);
        book.setPageCount(pageCount);
        return bookRepo.save(book);
    }

}

该文件负责对数据进行更新操作,因此分别定义了save、delete和update操作

  1. Query.java
package com.exaple.springboot_graphql.resolver;

import graphql.kickstart.tools.GraphQLQueryResolver;
import com.exaple.springboot_graphql.entity.Author;
import com.exaple.springboot_graphql.entity.Book;
import com.exaple.springboot_graphql.repo.AuthorRepo;
import com.exaple.springboot_graphql.repo.BookRepo;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * @author comqiao
 * @create 2020-10-17 12:40
 */
@Component
@AllArgsConstructor
public class Query implements GraphQLQueryResolver {

    private AuthorRepo authorRepo;

    private BookRepo bookRepo;

    public Author findAuthorById(Long id) {
        return authorRepo.findAuthorById(id);
    }

    public List<Author> findAllAuthors() {
        return authorRepo.findAll();
    }

    public Long countAuthors() {
        return authorRepo.count();
    }

    public List<Book> findAllBooks() {
        return bookRepo.findAll();
    }

    public Long countBooks() {
        return bookRepo.count();
    }
}

该文件负责实现对数据的查询

4.4.9 resources/graphql目录中的文件

该目录中的文件主要是完成GraphQL中的schema的类型定义,要让系统成功找到该定义文件,需要在前面所讲述的application.yml中定义该配置文件所在的位置:
项目学习GraphQL(四)_第11张图片
该目录中主要有以下两个文件:
1.schema.graphql
该文件就是使用GraphQL的类型定义语言所定义的schema,是整个GraphQL功能实现的核心,其内容如下:

type Author{
    id:ID!
    createdTime:String
    firstName:String
    lastName:String
    books:[Book]
}

input BookInput{
    title:String!
    isbn:String!
    pageCount:Int
    authorId:ID
}

type Book{
    id:ID!
    title:String
    isbn:String
    pageCount:Int
    author:Author
}

2.root.graphql
该文件的重要性在前面章节已经介绍过,它负责定义在GraphQL的查询语言中实现的语言的格式样式,其内容如下:

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

}

type Mutation {
    newAuthor(firstName: String!,lastName: String!) : Author!

    newBook(title: String!,isbn: String!,pageCount: Int, authorId: ID!) : Book!
    saveBook(input: BookInput!) : Book!
    deleteBook(id: ID!) : Boolean
    updateBookPageCount(pageCount: Int!, id:ID!) : Book!
}

至此,基于SpringBoot的GraphQL Server代码编写全部完成,下一章节将讲解如何利用GraphQL Playground对server进行调试

你可能感兴趣的:(GraphQL,graphql,spring,mysql)