Springboot中集成GraphQL

Springboot中集成GraphQL

1.前言

最近项目需求要使用GraphQL做为后端服务,提供API接口,奈何资料较少,在此记录一下搭建GraphQL服务的过程

2.工程环境

工程包含主要插件
JDK 1.8
Springboot 2.1.3
MySQL
MyBatis
Graphql-java
fastjson
Lombok
maven

3.创建Springboot工程

这里不在描述,使用IDEA或者Eclipse创建一个springboot工程或者导入一个Springboot工程都行,这里使用2.1.3.RELEASE版本

4.集成开发环境

4.1 配置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.1.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.mx.server</groupId>
    <artifactId>spring-boot-graphql</artifactId>
    <version>1.0.0-RELEASE</version>
    <name>spring-boot-graphql</name>
    <description>easy graphql for spring boot</description>

    <properties>
        <java.version>1.8</java.version>
        <kotlin.version>1.3.70</kotlin.version>
        <mybatis.version>2.0.0</mybatis.version>
        <c3p0.version>0.9.5.2</c3p0.version>
        <fastjson.version>1.2.5</fastjson.version>
        <junit.jupiter.version>5.6.2</junit.jupiter.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <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>
            <!-- 使用Junit5,排除spring-test自带的junit4 -->
            <exclusions>
                <exclusion>
                    <groupId>junit</groupId>
                    <artifactId>junit</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- GraphQL -->
        <dependency>
            <groupId>com.graphql-java-kickstart</groupId>
            <artifactId>graphql-spring-boot-starter</artifactId>
            <version>7.0.1</version>
        </dependency>
        <dependency>
            <groupId>com.graphql-java-kickstart</groupId>
            <artifactId>graphql-java-tools</artifactId>
            <version>6.0.2</version>
        </dependency>
        <!-- GraphiQL tool -->
        <dependency>
            <groupId>com.graphql-java-kickstart</groupId>
            <artifactId>graphiql-spring-boot-starter</artifactId>
            <version>7.0.1</version>
            <scope>runtime</scope>
        </dependency>
        <!-- Log4J2 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
        <!-- JDBC -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <!-- MySQL -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!-- Mybatis -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis.version}</version>
        </dependency>
        <!-- c3p0 -->
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>${c3p0.version}</version>
        </dependency>
        <!-- FastJSON -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!-- Junit5 -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>${junit.jupiter.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

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

</project>

其中GraphQL关键依赖参考GraphQL Java Kickstart官网

4.2 配置application.yml文件

Springboot的配置文件为applicaion.properties或者applicaion.yml格式,根据自己使用习惯选择即可,这里使用application.yml风格

#Server
server:
  port: 8080

#Mysql
spring:
  datasource:
    c3p0:
      driverClass: com.mysql.cj.jdbc.Driver
      jdbcUrl: jdbc:mysql://localhost:3306/graphql_test?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&useSSL=false
      user: mysql
      password: mxnavi

#Mybatis
mybatis:
  config-location: classpath:mybatis/mybatis-config.xml
  mapper-locations: classpath*:mybatis/mapper/**/*.xml

#GraphQL
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: "**/*.graphqls"
    # 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

#GraphiQL Tool
graphiql:
  mapping: /graphiql
  endpoint:
    graphql: /graphql
  subscriptions:
    timeout: 30
    reconnect: false
  static:
    basePath: /
  enabled: true
  pageTitle: GraphiQL
  props:
    resources:
      query: /graphql/types.graphqls
      variables: /graphql/types.graphqls
    variables:
      editorTheme: "solarized light"
  headers:
    Authorization: "Bearer <your-token>"

主要配置包括
Server部分:配置一些服务的基础,可定义端口号,服务名等
MySQL部分:配置连接MySQL数据库相关参数
MyBatis部分:配置MyBatis相关参数
GraphQL部分:配置GraphQL相关参数
GraphiQL Tool部分:配置GraphiQL工具相关参数,可访问图形化界面

4.3 创建graphqls文件

在resource目录下创建graphql文件夹,日后所有的graphqls文件都存放再此,创建types.graphqls文件
注意:此文件types.graphqls与applicaion.yml中配置有对应关系,如果修改,请记得一起修改,否则启动报错
types.graphqls文件内容如下

type Query {
    user(nickname: String!): User
    users: [User]
}
type Mutation {
    addUser(mail: String!, nickname: String!, password: String!, description: String): Result
    deleteUser(id: String!): Result
    updateUser(id: String!, mail: String!, nickname: String!, description: String): User
    addUserByInput(input: AddUserInput): User
}
type User {
    id: String!
    mail: String!
    nickname: String!
    password: String!
    description: String
}

type Result {
    respCode: Int!
    msg: String
}

input AddUserInput {
    mail: String!
    nickname: String!
    password: String!
    description: String
}

其中Query下定义的是查询相关接口,Mutation下定义的是修改相关接口
Query中user和users可以理解为controller中的接口名,后续需要和Resolver中方法名对应
关于graphqls文件中写法,具体参考GraphQL官网介绍GraphQL中文网

4.4 创建graphqls文件对应的Java对象

User.java

package com.mx.server.entity;

import lombok.Data;

@Data
public class User {
    private String id;
    private String nickname;
    private String mail;
    private String password;
    private String description;
    private String updateTime;
    private String createTime;
}

Result.java

package com.mx.server.entity.response;

import lombok.Data;

@Data
public class Result {
    private Integer respCode;
    private String msg;
}

AddUserInput.java

package com.mx.server.entity.input;

import lombok.Data;

@Data
public class AddUserInput {
    private String nickname;
    private String mail;
    private String password;
    private String description;
}

4.5 创建graphqls文件对应的Resolver对象

想实现graphqls文件中Query中的方法,就实现GraphQLQueryResolver
想实现graphqls文件中Mutation的中的方法,就实现GraphQLMutationResolver
这里为了区分Query和Mutation,分成2个Resolver来写,具体在项目中应用时根据实际情况来修改。
QueryResolver.Java

package com.mx.server.resolvers;

import com.mx.server.entity.User;
import com.mx.server.service.UserService;
import graphql.kickstart.tools.GraphQLQueryResolver;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;

@Component
public class QueryResolver implements GraphQLQueryResolver {

    private static final Logger logger = LogManager.getLogger(QueryResolver.class);

    @Resource
    private UserService userService;

    public User user(String nickname) {
        logger.info("Query Resolver ==> user");
        logger.info("params: nickname:{}", nickname);
        return userService.getUserByNickname(nickname);
    }

    public List<User> users() {
        logger.info("Query Resolver ==> users");
        return userService.listUsers();
    }
}

MutationResolver.java

package com.mx.server.resolvers;

import com.mx.server.entity.User;
import com.mx.server.entity.input.AddUserInput;
import com.mx.server.entity.response.ResponseBuilder;
import com.mx.server.entity.response.Result;
import com.mx.server.service.UserService;
import graphql.kickstart.tools.GraphQLMutationResolver;
import graphql.kickstart.tools.GraphQLQueryResolver;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

@Component
public class MutationResolver implements GraphQLQueryResolver, GraphQLMutationResolver {

    private static final Logger logger = LogManager.getLogger(MutationResolver.class);

    @Resource
    private UserService userService;

    public Result addUser(String mail, String nickname, String password, String description) {
        logger.info("Mutation Resolver ==> addUser");
        logger.info("params: mail:{}, nickname:{}, password:{}, description:{}",
                mail, nickname, password, description);
        return userService.addUser(mail, nickname, password, description);
    }

    public Result deleteUser(String id) {
        if(StringUtils.isAnyBlank(id)){
            return ResponseBuilder.getRespCodeMsg(-101, "参数缺失");
        }
        logger.info("Mutation Resolver ==> deleteUser");
        logger.info("params: id:{}", id);
        return userService.deleteUser(id);
    }
    
    public User updateUser(String id, String mail, String nickname, String description) {
        logger.info("Mutation Resolver ==> updateUser");
        logger.info("params: id:{}, mail:{}, nickname:{}, description:{}",
                id, mail, nickname, description);
        return userService.updateUser(id, mail, nickname, description);
    }

    public User addUserByInput(AddUserInput addUserInput){
        logger.info("Mutation Resolver ==> addUserByInput");
        logger.info("params: {}", addUserInput);
        return userService.addUserInput(addUserInput);
    }
}

注意:Resolver中的方法,入参及返回值类型,必须和graphqls文件中定义的一致,否则启动报错。
至此与GraphQL相关的配置及代码都已创建完成,接下来把工程中其他的类都一一实现,比如Server,Dao等。

4.6 创建Service,Dao等实现类

UserService.java -> Service类,通过Resolver调用,简单加了一些验证。

package com.mx.server.service;

import com.mx.server.dao.UserMapper;
import com.mx.server.entity.User;
import com.mx.server.entity.input.AddUserInput;
import com.mx.server.entity.response.ResponseBuilder;
import com.mx.server.entity.response.Result;
import com.mx.server.util.CommonUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

@Service
public class UserService {

    @Resource
    private UserMapper userMapper;

    private static final Logger logger = LogManager.getLogger(UserService.class.getName());

    public User getUserByNickname(String nickname){
        logger.info("Service ==> getUserByNickname");
        return userMapper.getUserByNickname(nickname);
    }

    public List<User> listUsers(){
        logger.info("Service ==> listUsers");
        return userMapper.listUsers();
    }

    public Result addUser(String mail, String nickname, String password, String description){
        logger.info("Service ==> getUser");

        User userDb = userMapper.getUserByNickname(nickname);
        if (null != userDb){
            return ResponseBuilder.getRespCodeMsg(-110, "用户昵称存在");
        }

        User addUser = new User();
        addUser.setId(CommonUtils.getUUID());
        addUser.setMail(mail);
        addUser.setNickname(nickname);
        addUser.setPassword(password);
        addUser.setDescription(description);

        userMapper.addUser(addUser);

        return ResponseBuilder.getRespCodeMsg(100, "Success");
    }

    public Result deleteUser(String id){
        logger.info("Service ==> deleteUser");

        User user = userMapper.getUserById(id);
        if (null == user){
            return ResponseBuilder.getRespCodeMsg(-500, "数据不存在");
        }

        userMapper.deleteUser(id);
        return ResponseBuilder.getRespCodeMsg(100, "Success");
    }

    public User updateUser(String id,String mail, String nickname, String description){
        logger.info("Service ==> updateUser");
        User updateUser = new User();
        updateUser.setId(id);
        updateUser.setMail(mail);
        updateUser.setNickname(nickname);
        updateUser.setDescription(description);

        userMapper.updateUser(updateUser);

        return updateUser;
    }

    public User addUserInput(AddUserInput addUserInput){
        logger.info("Service ==> addUserInput");
        User addUser = new User();
        addUser.setId(CommonUtils.getUUID());
        addUser.setMail(addUserInput.getMail());
        addUser.setNickname(addUserInput.getNickname());
        addUser.setPassword(addUserInput.getPassword());
        addUser.setDescription(addUserInput.getDescription());

        userMapper.addUser(addUser);

        return addUser;
    }
}

UserMapper.java -> Mapper接口类,定义调用数据库的方法,由Service调用。

package com.mx.server.dao;

import com.mx.server.entity.User;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface UserMapper {
    User getUserById(String id);
    User getUserByNickname(String nickname);
    List<User> listUsers();
    void addUser(User user);
    void deleteUser(String id);
    void updateUser(User user);
}

CommonUtils.java -> 工具类,定义一些常用公共方法

package com.mx.server.util;

import java.util.UUID;

public final class CommonUtils {

    private CommonUtils() {
    }

    public static String getUUID() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }
}

DataSourceConfig -> 定义数据源

package com.mx.server.config;

import javax.sql.DataSource;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

import com.mchange.v2.c3p0.ComboPooledDataSource;

@Configuration
// 声明为配置类,相当于<beans>标签
@Component
public class DataSourceConfig {

    @Bean(name = "dataSource")
    // 对象及名称,相当于<bean>标签
    @Primary
    // 主要的候选者
    // 配置属性,prefix : 前缀 spring.datasource固定
    @ConfigurationProperties(prefix = "spring.datasource.c3p0")
    public DataSource createDataSource() {
        return DataSourceBuilder.create() // 创建数据源构建对象
                .type(ComboPooledDataSource.class) // 设置数据源类型
                .build(); // 构建数据源对象
    }

}

4.7 MyBatis配置

本工程使用了MyBatis,上面已经定义好了Mapper类,下面就来创建mapper对应的xml文件实现对MySql的CRUD操作。
applicaion.yml文件中的MyBatis配置如下:

#Mybatis
mybatis:
  config-location: classpath:mybatis/mybatis-config.xml
  mapper-locations: classpath*:mybatis/mapper/**/*.xml

在resource目录下创建mybatis文件夹,放入mybatis-config.xml,其实没什么有用的内容,简单定义一些返回类型的别名。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <typeAliases>
        <typeAlias alias="Integer" type="java.lang.Integer" />
        <typeAlias alias="Long" type="java.lang.Long" />
        <typeAlias alias="Boolean" type="java.lang.Boolean" />
        <typeAlias alias="HashMap" type="java.util.HashMap" />
        <typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap" />
        <typeAlias alias="ArrayList" type="java.util.ArrayList" />
        <typeAlias alias="LinkedList" type="java.util.LinkedList" />
    </typeAliases>
</configuration>

在mybatis文件夹下创建mapper文件夹,用来存放具体实现mapper的xml文件
userMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mx.server.dao.UserMapper">
    <select id="getUserByNickname" resultType="com.mx.server.entity.User">
        SELECT
            id,
            nickname,
            mail,
            description,
            DATE_FORMAT(update_time,'%Y-%m-%d %H:%i') updateTime,
            DATE_FORMAT(create_time,'%Y-%m-%d %H:%i') createTime
        FROM
            user
        WHERE
            nickname = #{nickname}
    </select>

    <select id="getUserById" resultType="com.mx.server.entity.User">
        SELECT
            id,
            nickname,
            mail,
            description,
            DATE_FORMAT(update_time,'%Y-%m-%d %H:%i') updateTime,
            DATE_FORMAT(create_time,'%Y-%m-%d %H:%i') createTime
        FROM
            user
        WHERE
            id = #{id}
    </select>

    <select id="listUsers" resultType="com.mx.server.entity.User">
        SELECT
            id id,
            nickname nickname,
            mail mail,
            description description,
            DATE_FORMAT(update_time,'%Y-%m-%d %H:%i:%s') updateTime,
            DATE_FORMAT(create_time,'%Y-%m-%d %H:%i:%s') createTime
        FROM
            user
    </select>

    <insert id="addUser" parameterType="com.mx.server.entity.User"
            keyProperty="id" useGeneratedKeys="true">
        INSERT INTO
            user
        (
            id,
            nickname,
            password,
            mail,
            description,
            update_time,
            create_time
        )
        VALUES
        (
            #{id},
            #{nickname},
            #{password},
            #{mail},
            #{description},
            now(),
            now()
        )
    </insert>

    <update id="updateUser">
        UPDATE
            user
        SET
            nickname = #{nickname},
            mail = #{mail},
            description = #{description},
            update_time = now()
        WHERE
            id = #{id}
    </update>

    <delete id="deleteUser">
        DELETE FROM
            user
        WHERE
            id = #{id}
    </delete>
</mapper>

user表结构

CREATE TABLE `user` (
  `id` varchar(32) NOT NULL,
  `nickname` varchar(255) NOT NULL,
  `mail` varchar(255) NOT NULL,
  `password` varchar(255) NOT NULL,
  `description` varchar(255) DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  `create_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

最终工程目录结构如下
在这里插入图片描述

5.启动和测试执行

使用GraphiQL Tool进行API调用测试,启动Application,成功后,打开浏览器访问 http://localhost:8080/graphiql
http://localhost:8080/graphiql
默认显示工程里的graphqls文件内容,删除后,可在右上角Docs菜单查看

5.1 Query类型调用

调用查询接口users,输入调用内容,点击执行按钮,查看结果

# query 查询关键字
# ListUsers 别名
# users 接口名
# id,nickname 查询后,想要获取的返回值
query ListUsers{
    users {
      id
      nickname
    }
}

执行结果
users

5.2 Mutation类型调用

调用创建接口addUser,输入调用内容,点击执行按钮,查看结果

# mutation 变更关键字
# AddUser 别名
# addUser 接口名,后面接入参,接口中有!定义的变量是必填的,其他随意
# respCode,msg 想要获取的返回值
mutation AddUser{
    addUser(mail:"11", nickname: "testuser", password: "111", description: "test") {
      respCode
      msg
    }
}

执行结果
addUser

5.3 使用变量Variables

拿根据昵称查询用户做示例

# QueryUser 别名
# query 关键字后面的 $nickname 为声明变量
# user 后面的 $nickname 为使用变量,变量名要与上面声明的一致
query QueryUser($nickname: String!){
    user(nickname: $nickname) {
      id
      nickname
    }
}

在Variables中写入变量具体的值,格式为JSON格式,这里输入刚刚创建的用户,昵称为testuser

{
  "nickname": "testuser"
}

执行结果
在这里插入图片描述

5.4 input输入类型的使用

在实际应用时,如果入参过多,逐个填写,显示过于太长了,使用起来也不方便,下面来演示使用input类型,将入参封装成对象传入,接口列表中定义了addUserByInput方法,入参类型为input

# mutation 变更关键字
# AddUserByInput 别名
# mutation后$userinput,声明userinput变量,类型为AddUserInput
# 变量的类型AddUserInput,已经在graphqls文件中定义,input开头那个
# addUserByInput 接口名
# addUserByInput后input 入参类型 
# addUserByInput后$userinput 入参变量引用
mutation AddUserByInput($userinput: AddUserInput) {
  addUserByInput(input: $userinput) {
    id
    nickname
    mail
  }
}

在Variables中写入变量userinput具体的值,格式为JSON格式

{
  "userinput": {
    "mail": "testuser2@test.com",
    "nickname": "testuser2",
    "password": "123456"
  }
}

执行结果
addUserByInput
到此基本功能演示结束,好了,接下就根据官网探索更多的使用方式吧!

6.源码

GitHub源码地址

  • 9
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值