Springboot中集成GraphQL

目录

  • Springboot中集成GraphQL
    • 1.前言
    • 2.工程环境
    • 3.创建Springboot工程
    • 4.集成开发环境
      • 4.1 配置pom.xml文件
      • 4.2 配置application.yml文件
      • 4.3 创建graphqls文件
      • 4.4 创建graphqls文件对应的Java对象
      • 4.5 创建graphqls文件对应的Resolver对象
      • 4.6 创建Service,Dao等实现类
      • 4.7 MyBatis配置
    • 5.启动和测试执行
      • 5.1 Query类型调用
      • 5.2 Mutation类型调用
      • 5.3 使用变量Variables
      • 5.4 input输入类型的使用
    • 6.源码

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.DriverjdbcUrl: jdbc:mysql://localhost:3306/graphql_test?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&useSSL=falseuser: mysqlpassword: mxnavi#Mybatis
mybatis:config-location: classpath:mybatis/mybatis-config.xmlmapper-locations: classpath*:mybatis/mapper/**/*.xml#GraphQL
graphql:servlet:mapping: /graphqlenabled: truecorsEnabled: truecors:allowed-origins: http://some.domain.com# if you want to @ExceptionHandler annotation for custom GraphQLErrorsexception-handlers-enabled: truecontextSetting: PER_REQUEST_WITH_INSTRUMENTATIONtools: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: /graphiqlendpoint:graphql: /graphqlsubscriptions:timeout: 30reconnect: falsestatic:basePath: /enabled: truepageTitle: GraphiQLprops:resources:query: /graphql/types.graphqlsvariables: /graphql/types.graphqlsvariables:editorTheme: "solarized light"headers:Authorization: "Bearer "

主要配置包括
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!): Userusers: [User]
}
type Mutation {addUser(mail: String!, nickname: String!, password: String!, description: String): ResultdeleteUser(id: String!): ResultupdateUser(id: String!, mail: String!, nickname: String!, description: String): UseraddUserByInput(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);@Resourceprivate 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);@Resourceprivate 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 {@Resourceprivate 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
// 声明为配置类,相当于标签
@Component
public class DataSourceConfig {@Bean(name = "dataSource")// 对象及名称,相当于标签@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.xmlmapper-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">SELECTid,nickname,mail,description,DATE_FORMAT(update_time,'%Y-%m-%d %H:%i') updateTime,DATE_FORMAT(create_time,'%Y-%m-%d %H:%i') createTimeFROMuserWHEREnickname = #{nickname}</select><select id="getUserById" resultType="com.mx.server.entity.User">SELECTid,nickname,mail,description,DATE_FORMAT(update_time,'%Y-%m-%d %H:%i') updateTime,DATE_FORMAT(create_time,'%Y-%m-%d %H:%i') createTimeFROMuserWHEREid = #{id}</select><select id="listUsers" resultType="com.mx.server.entity.User">SELECTid 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') createTimeFROMuser</select><insert id="addUser" parameterType="com.mx.server.entity.User"keyProperty="id" useGeneratedKeys="true">INSERT INTOuser(id,nickname,password,mail,description,update_time,create_time)VALUES(#{id},#{nickname},#{password},#{mail},#{description},now(),now())</insert><update id="updateUser">UPDATEuserSETnickname = #{nickname},mail = #{mail},description = #{description},update_time = now()WHEREid = #{id}</update><delete id="deleteUser">DELETE FROMuserWHEREid = #{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 {idnickname}
}

执行结果
users

5.2 Mutation类型调用

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

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

执行结果
addUser

5.3 使用变量Variables

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

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

在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) {idnicknamemail}
}

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

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

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

6.源码

GitHub源码地址


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部