原文地址
GraphQL 是一种用于 API 的查询语言,使得客户端能够准确地获得它需要的数据,而且没有任何冗余。GraphQL是一种强类型协议,所有数据操作都会根据 GraphQL Schema 来进行校验。
在本文中,我们将使用Spring Boot构建一个简单的GraphQL服务器。
创建示例Spring Boot应用程序并添加以下依赖项。
graphql-spring-boot-starter
用于启用 GraphQL 控制器,并使其在 path/graphql 中可用。它将初始化GraphQL Schema bean。 复制代码
graphql-java
可以使用易于理解的Graphql Schema 语言来编写 schema。 复制代码
graphiql-spring-boot-starter
提供图形界面,我们可以使用它来测试 GraphQL 查询和查看查询定义。 复制代码
<dependency> <groupId>com.graphql-java</groupId> <artifactId>graphql-spring-boot-starter</artifactId> <version>5.0.2</version> </dependency> <dependency> <groupId>com.graphql-java</groupId> <artifactId>graphql-java-tools</artifactId> <version>5.2.4</version> </dependency> <dependency> <groupId>com.graphql-java</groupId> <artifactId>graphiql-spring-boot-starter</artifactId> <version>5.0.2</version> </dependency> 复制代码
以下完整的POM文件内容。
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.techshard.graphql</groupId> <artifactId>springboot-graphql</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> <relativePath /> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.graphql-java</groupId> <artifactId>graphql-spring-boot-starter</artifactId> <version>5.0.2</version> </dependency> <dependency> <groupId>com.graphql-java</groupId> <artifactId>graphql-java-tools</artifactId> <version>5.2.4</version> </dependency> <dependency> <groupId>com.graphql-java</groupId> <artifactId>graphiql-spring-boot-starter</artifactId> <version>5.0.2</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.8</version> <optional>true</optional> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> 复制代码
接下来让我们创建一个名为Vehicle的简单实体和相应的JPA存储库。我们将使用Lombok来编写,这样可以避免编写诸如getter和setter等等的样板文件。
package com.techshard.graphql.dao.entity; import lombok.Data; import lombok.EqualsAndHashCode; import javax.persistence.*; import java.io.Serializable; import java.time.LocalDate; @Data @EqualsAndHashCode @Entity public class Vehicle implements Serializable { private static final long serialVersionUID = 1L; @Id @Column(name = "ID", nullable = false) @GeneratedValue(strategy = GenerationType.AUTO) private int id; @Column(name = "type", nullable = false) private String type; @Column(name = "model_code", nullable = false) private String modelCode; @Column(name = "brand_name") private String brandName; @Column(name = "launch_date") private LocalDate launchDate; private transient String formattedDate; // Getter and setter public String getFormattedDate() { return getLaunchDate().toString(); } } 复制代码
然后是相应的JPA存储库。
package com.techshard.graphql.dao.repository; import com.techshard.graphql.dao.entity.Vehicle; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface VehicleRepository extends JpaRepository<Vehicle, Integer> { } 复制代码
GraphQL 用有自己的独特语言来编写GraphQL Schema,称为 Schema Definition Language (SDL)。schema 的定义,是由端点上所有可用的 API 功能组成的。
GraphQL架构的典型示例如下所示:
type Vehicle { id: ID!, type: String, modelCode: String, brandName: String, launchDate: String } type Query { vehicles(count: Int):[Vehicle] vehicle(id: ID):Vehicle } type Mutation { createVehicle(type: String!, modelCode: String!, brandName: String, launchDate: String):Vehicle } 复制代码
接下来,在 src/main/resources 下创建一个文件夹,名为 graphql,并在该文件夹下创建vehicleql.graphqls文件。复制上面的内容并将其粘贴到vehicleql.graphqls文件中。需要注意的是,文件名称是可自定义的。以.graphqls作为文件扩展名即可。
在上面的 schema 中,每个对象都是用类型定义的。 GraphQL 中的类型系统是最基本的组件,它表示可以从服务获取的对象以及该对象所包含的字段。
在我们的 schema 中,我们有一个名为 Vehicle 的对象,作为域对象。 Query 类型表示可通过 GraphQL 服务器获取数据的查询。query 是交互式的,可修改,修改后即可看到新的结果。query 和结果的结构是一样的。这一点在 GraphQL 中很重要,因为所见即所得。
在稍后的文章中,我们可以看到一些真正运行的例子。
Mutation 类型用来表示那些用于对数据执行写入操作的 query。
Query 或 Mutation对象是基本 GraphQL 对象,它们没有任何关联的数据类。在这种情况下,解析器类将实现 GraphQLQueryResolver 或 GraphQLMutationResolver。These resolvers will be searched for methods that map to fields in their respective root types.(这段不是很懂,所以先贴原文)
接下来,让我们为Vehicle定义根解析器。
package com.techshard.graphql.query; import com.coxautodev.graphql.tools.GraphQLQueryResolver; import com.techshard.graphql.dao.entity.Vehicle; import com.techshard.graphql.service.VehicleService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List; import java.util.Optional; @Component public class VehicleQuery implements GraphQLQueryResolver { @Autowired private VehicleService vehicleService; public List<Vehicle> getVehicles(final int count) { return this.vehicleService.getAllVehicles(count); } public Optional<Vehicle> getVehicle(final int id) { return this.vehicleService.getVehicle(id); } } 复制代码
在这个类中,我们有方法来获取单个Vehicle对象和Vehicle对象列表。请注意,我们在上面的模式中定义了这些方法。
现在,让我们定义一个Mutation解析器。
package com.techshard.graphql.mutation; import com.coxautodev.graphql.tools.GraphQLMutationResolver; import com.techshard.graphql.dao.entity.Vehicle; import com.techshard.graphql.service.VehicleService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.time.LocalDate; @Component public class VehicleMutation implements GraphQLMutationResolver { @Autowired private VehicleService vehicleService; public Vehicle createVehicle(final String type, final String modelCode, final String brandName, final String launchDate) { return this.vehicleService.createVehicle(type, modelCode, brandName, launchDate); } } 复制代码
在这个类中,我们只有一个方法来创建一个Vehicle对象,这对应于我们的模式定义中的Mutation类型。
我们现在将定义一个可以进行实际交互的服务。
package com.techshard.graphql.service; import com.techshard.graphql.dao.entity.Vehicle; import com.techshard.graphql.dao.repository.VehicleRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @Service public class VehicleService { private final VehicleRepository vehicleRepository ; public VehicleService(final VehicleRepository vehicleRepository) { this.vehicleRepository = vehicleRepository ; } @Transactional public Vehicle createVehicle(final String type,final String modelCode, final String brandName, final String launchDate) { final Vehicle vehicle = new Vehicle(); vehicle.setType(type); vehicle.setModelCode(modelCode); vehicle.setBrandName(brandName); vehicle.setLaunchDate(LocalDate.parse(launchDate)); return this.vehicleRepository.save(vehicle); } @Transactional(readOnly = true) public List<Vehicle> getAllVehicles(final int count) { return this.vehicleRepository.findAll().stream().limit(count).collect(Collectors.toList()); } @Transactional(readOnly = true) public Optional<Vehicle> getVehicle(final int id) { return this.vehicleRepository.findById(id); } } 复制代码
现在,我们可以来测试一下这个应用了。运行Spring Boot 应用程序。在浏览器中打开 http//localhost8080/graphiql 链接。我们将看到一个友好的图形见面,如下图所示。
在用户界面的右侧,我们还可以看到文档。
现在,我们可以尝试运行以下查询。
mutation { createVehicle(type: "car", modelCode: "XYZ0192", brandName: "XYZ", launchDate: "2016-08-16") { id } } 复制代码
这将在Vehicle表中创建一行数据。结果为:
{ "data": { "createVehicle": { "id": "1" } } } 复制代码
现在让我们运行查询来获取数据。
query { vehicles(count: 1) { id, type, modelCode } } 复制代码
然后我们将得到结果
{ "data": { "vehicles": [ { "id": "1", "type": "bus", "modelCode": "XYZ123" } ] } } 复制代码
请注意,我们仅请求有限数量的字段。我们可以通过添加或删除字段来更改查询,并查看新结果。