type Product {
id: ID!
info: String!
price: Float
}
在此示例中,声明了Product对象类型,定义了3 个字段:
id:非空 ID 类型。
info:非空字符串类型。
price:浮点型。
interface Product {
id: ID!
info: String!
price: Float
}
enum Status {
Yes
No
}
type Product {
id: ID!
info: String!
price: Float
stat: Status
}
input BookInput {
isbn: ID!
title: String!
pages: Int
authorIdCardNo: String
}
type Product {
id: ID!
info: String
price: Float
images: [String]
}
#schema.graphqls定义操作
type Query {
allBooks: [Book]!
bookByIsbn(isbn: ID): Book
}
# 接口查询语法
query{
allBooks {
title
author {
name
age
}
}
}
#schema.graphqls定义操作
type Mutation {
createBook(bookInput: BookInput): Book
createAuthor(authorInput: AuthorInput): Author
}
# mutation{
# createAuthor(authorInput:{
# idCardNo: "341234567891234567",
# name:"test1",
# age:38
# }
# ){
# name
# age
# }
# }
type Subscription {
greetings: String
}
GraphQL只是一种架构设计,具体的实现需要各个技术平台自己实现,目前主流的开发语言基本都已经有现成的类库可以使用,GraphQL Java就是Java平台的实现。
GraphQL Java虽然实现了GraphQL,但是只是一个执行GraphQL请求的引擎,用户在使用中需要创建自己的HTTP服务来提供服务。
Spring for GraphQL为基于GraphQL Java构建的Spring应用程序提供支持,来自 GraphQL Java 团队,它的目标是成为所有Spring、GraphQL应用程序的基础。
spring-graphql中定义的核心注解如下:
数据表结构,这里例子简单用了用户表和用户日志表
CREATE TABLE `admin_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4
CREATE TABLE `admin_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) DEFAULT NULL,
`visit_url` varchar(255) DEFAULT NULL,
`create_date` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4
GraphQL对应的schema.graphql定义文件,注意GraphQL默认只支持标量类型,DateTime自定义类型使用graphql-java-extended-scalars:https://github.com/graphql-java/graphql-java-extended-scalars提供
scalar DateTime
type AdminUser{
id: ID!
name: String!
}
type AdminLog{
id: ID!
visitUrl: String
user: AdminUser!
createDate: DateTime
}
type Query {
allLogs:[AdminLog]
logByUser(userid: ID): [AdminLog]
}
type Mutation {
createUser(adminUserInput: AdminUserInput): AdminUser
createLog(adminLogInput: AdminLogInput): AdminLog
}
input AdminLogInput {
userId: String!
visitUrl: String
createDate: DateTime
}
input AdminUserInput {
name: String!
}
type Subscription {
greetings: String
}
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>3.0.10version>
<relativePath/>
parent>
<groupId>com.penngo.examplegroupId>
<artifactId>graphql-appartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>graphql-appname>
<description>graphql-app project for Spring Bootdescription>
<properties>
<java.version>17java.version>
<maven.compiler.source>17maven.compiler.source>
<maven.compiler.target>17maven.compiler.target>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-graphqlartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.23version>
dependency>
<dependency>
<groupId>com.graphql-javagroupId>
<artifactId>graphql-java-extended-scalarsartifactId>
<version>19.1version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
exclude>
excludes>
configuration>
plugin>
plugins>
build>
<repositories>
<repository>
<id>alimavenid>
<name>Maven Aliyun Mirrorname>
<url>https://maven.aliyun.com/repository/centralurl>
repository>
repositories>
<pluginRepositories>
<pluginRepository>
<id>aliyun-pluginid>
<url>https://maven.aliyun.com/repository/publicurl>
<releases>
<enabled>trueenabled>
releases>
<snapshots>
<enabled>falseenabled>
snapshots>
pluginRepository>
pluginRepositories>
project>
对应的数据库实体类
package com.penngo.example.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.OffsetDateTime;
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AdminLog {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
private String userId;
private String visitUrl;
private OffsetDateTime createDate;
}
package com.penngo.example.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AdminUser {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
private String name;
}
GraphQL对应的输入类
package com.penngo.example.entity;
import lombok.Data;
import java.time.OffsetDateTime;
@Data
public class AdminLogInput {
private String userId;
private String visitUrl;
private OffsetDateTime createDate;
}
package com.penngo.example.entity;
import lombok.Data;
@Data
public class AdminUserInput {
private String name;
}
对应的数据库操作类
package com.penngo.example.repository;
import com.penngo.example.entity.AdminUser;
import org.springframework.data.jpa.repository.JpaRepository;
public interface AdminUserRepository extends JpaRepository<AdminUser,Long> {
AdminUser findById(String userId);
}
package com.penngo.example.repository;
import com.penngo.example.entity.AdminLog;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface AdminLogRepository extends JpaRepository<AdminLog,Long> {
List<AdminLog> findAllByUserId(String userId);
}
对外服务接口类
package com.penngo.example.controller;
import com.penngo.example.entity.*;
import com.penngo.example.repository.AdminLogRepository;
import com.penngo.example.repository.AdminUserRepository;
import org.springframework.beans.BeanUtils;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.MutationMapping;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.data.method.annotation.SchemaMapping;
import org.springframework.stereotype.Controller;
import java.util.List;
@Controller
public class AdminLogController {
private final AdminUserRepository adminUserRepository;
private final AdminLogRepository adminLogRepository;
public AdminLogController(AdminUserRepository adminUserRepository, AdminLogRepository adminLogRepository){
this.adminLogRepository = adminLogRepository;
this.adminUserRepository = adminUserRepository;
}
@QueryMapping
public List<AdminLog> allLogs(){
List<AdminLog> logsList = adminLogRepository.findAll();
return logsList;
}
@QueryMapping
public List<AdminLog> logByUser(@Argument String userid){
return adminLogRepository.findAllByUserId(userid);
}
@SchemaMapping(typeName = "AdminLog" ,field = "user")
public AdminUser getAuthor(AdminLog adminLog){
AdminUser adminUser = adminUserRepository.findById(adminLog.getUserId());
return adminUser;
}
@MutationMapping
public AdminLog createLog(@Argument AdminLogInput adminLogInput){
AdminLog log = new AdminLog();
BeanUtils.copyProperties(adminLogInput,log);
return adminLogRepository.save(log);
}
}
package com.penngo.example.controller;
import com.penngo.example.entity.*;
import com.penngo.example.repository.AdminLogRepository;
import com.penngo.example.repository.AdminUserRepository;
import org.springframework.beans.BeanUtils;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.MutationMapping;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.stereotype.Controller;
import java.util.List;
@Controller
public class AdminUserController {
private final AdminUserRepository adminUserRepository;
private final AdminLogRepository adminLogRepository;
public AdminUserController(AdminUserRepository adminUserRepository, AdminLogRepository adminLogRepository){
this.adminLogRepository = adminLogRepository;
this.adminUserRepository = adminUserRepository;
}
@QueryMapping
public List<AdminLog> userById(@Argument String userid){
return adminLogRepository.findAllByUserId(userid);
}
@MutationMapping
public AdminUser createUser(@Argument AdminUserInput adminUserInput){
AdminUser user = new AdminUser();
BeanUtils.copyProperties(adminUserInput,user);
return adminUserRepository.save(user);
}
}
package com.penngo.example.controller;
import com.penngo.example.entity.AdminLog;
import com.penngo.example.repository.AdminLogRepository;
import com.penngo.example.repository.AdminUserRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.graphql.data.method.annotation.SubscriptionMapping;
import org.springframework.stereotype.Controller;
import reactor.core.publisher.Flux;
import java.time.Duration;
@Controller
public class GreetingController {
private final AdminUserRepository adminUserRepository;
private final AdminLogRepository adminLogRepository;
public GreetingController(AdminUserRepository adminUserRepository, AdminLogRepository adminLogRepository){
this.adminLogRepository = adminLogRepository;
this.adminUserRepository = adminUserRepository;
}
// 数据订阅,取最新的5条数据,每5秒发送一条给客户端,一共5次
@SubscriptionMapping
public Flux<AdminLog> greetings(){
System.out.println("greetings====================");
Page<AdminLog> logsList = adminLogRepository.findAll(PageRequest.of(0,5).withSort(Sort.Direction.DESC, "id"));
return Flux.fromStream(logsList.stream())
.delayElements(Duration.ofSeconds(5))
.take(5);
}
}
自定义日期数据类型DateTime
package com.penngo.example.component;
import graphql.scalars.ExtendedScalars;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
@Configuration
public class CustomScalarType {
@Bean
public RuntimeWiringConfigurer runtimeWiringConfigurer() {
return wiringBuilder -> wiringBuilder.scalar(ExtendedScalars.DateTime);
}
}
服务启动类
package com.penngo.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GraphqlAppApplication {
public static void main(String[] args) {
SpringApplication.run(GraphqlAppApplication.class, args);
}
}
添加用户
mutation{
createUser(adminUserInput: {
name: "test1",
} )
{
id
name
} }
添加日志
mutation{
createLog(adminLogInput: {
userId: "1",
visitUrl: "http://localhost:8080/method1"
createDate: "2023-09-17T19:39:57+08:00"
} )
{
id
visitUrl
createDate
} }
查询所有日志
query{
allLogs{
id
visitUrl
user{
id
name
}
createDate
}
}
查询指定用户日志
query{
logByUser(userid:"1") {
id
visitUrl
user{
id
name
}
createDate
}
}
数据订阅,订阅需要有websocket的支持。
subscription {
greetings
}
使用Spring for GraphQL试用了GraphQL后,它实现按需取数据的功能。服务器开发人员和前端开发人员可以通过schema.graphqls定义文件,协定好接口和数据,省掉写接口文档的工作。
缺点可能就是需要一点学习成本,虽然提供数据嵌套可以通过一个请求获取所有数据,但是嵌套复杂可能引起性能问题。
Spring for GraphQL官方参考:https://docs.spring.io/spring-graphql/docs/current/reference/html/#overview