<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/>
</parent>
<artifactId>springboot.graphql.app</artifactId>
<name>springboot-graphql-app</name>
<description>Demo project for Spring Boot with Graph QL</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-spring-boot-starter</artifactId>
<version>3.6.0</version>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-tools</artifactId>
<version>3.2.0</version>
</dependency>
</dependencies>

添加EndPoint
讓我們從BookController開始,如下所示。

package graphqlapp.controller;
import graphqlapp.service.GraphQLService;
import graphql.ExecutionResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping(“/rest/books”)
@RestController
public class BookController {
private static Logger logger = LoggerFactory.getLogger(BookController.class);
private GraphQLService graphQLService;
@Autowired
public BookController(GraphQLService graphQLService) {
this.graphQLService=graphQLService;
}
@PostMapping
public ResponseEntity<Object> getAllBooks(@RequestBody String query){
logger.info(“Entering getAllBooks@BookController”);
ExecutionResult execute = graphQLService.getGraphQL().execute(query);
return new ResponseEntity<>(execute, HttpStatus.OK);
}
}

添加model
接下來,我們將添加一個model類來代表書。 我們將其命名為Book。 model類的代碼如下。

package graphqlapp.model;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Table
@Entity
public class Book {
@Id
private String isn;
private String title;
private String publisher;
private String publishedDate;
private String[] author;
public Book() {
}
public Book(String isn, String title, String publisher, String publishedDate, String[] author) {
this.isn = isn;
this.title = title;
this.publisher = publisher;
this.publishedDate = publishedDate;
this.author = author;
}
public String getIsn() {
return isn;
}
public void setIsn(String isn) {
this.isn = isn;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getPublisher() {
return publisher;
}
public void setPublisher(String publisher) {
this.publisher = publisher;
}
public String getPublishedDate() {
return publishedDate;
}
public void setPublishedDate(String publishedDate) {
this.publishedDate = publishedDate;
}
public String[] getAuthor() {
return author;
}
public void setAuthor(String[] author) {
this.author = author;
}
}

創建BookRepository
BookRepository擴展了JpaRepository,如下所示。

package graphqlapp.repository;
import graphqlapp.model.Book;
import org.springframework.data.jpa.repository.JpaRepository;
public interface BookRepository extends JpaRepository<Book, String> {
}

添加GraphQL模式(schema)
接下來,我們將在資源文件夾中編寫一個GraphQL模式,名為books.graphql。

schema{
query:Query
}type Query{
allBooks: [Book]
book(id: String): Book
}type Book{
isn:String
title:String
publisher:String
author:[String]
publishedDate:String
}

該文件是使用GraphQL的關鍵。 在這里,我們定義了模式,你可以將其與查詢相關聯。 我們還定義了查詢類型。
在此示例中,我們定義了兩種類型:

添加GraphQL服務

接下來,我們需要添加GraphQL服務。 讓我們將其命名為GraphQLService。

package graphqlapp.service;
import graphqlapp.model.Book;
import graphqlapp.repository.BookRepository;
import graphqlapp.service.datafetcher.AllBooksDataFetcher;
import graphqlapp.service.datafetcher.BookDataFetcher;
import graphql.GraphQL;
import graphql.schema.GraphQLSchema;
import graphql.schema.idl.RuntimeWiring;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.io.File;
import java.io.IOException;
import java.util.stream.Stream;
@Service
public class GraphQLService {
private static Logger logger = LoggerFactory.getLogger(GraphQLService.class);
private BookRepository bookRepository;
private AllBooksDataFetcher allBooksDataFetcher;
private BookDataFetcher bookDataFetcher;
@Value(“classpath:books.graphql”)
Resource resource;
private GraphQL graphQL;
@Autowired
public GraphQLService(BookRepository bookRepository, AllBooksDataFetcher allBooksDataFetcher,
BookDataFetcher bookDataFetcher) {
this.bookRepository=bookRepository;
this.allBooksDataFetcher=allBooksDataFetcher;
this.bookDataFetcher=bookDataFetcher;
}
@PostConstruct
private void loadSchema() throws IOException {
logger.info(“Entering loadSchema@GraphQLService”);
loadDataIntoHSQL();
//Get the graphql file
File file = resource.getFile();
//Parse SchemaF
TypeDefinitionRegistry typeDefinitionRegistry = new SchemaParser().parse(file);
RuntimeWiring runtimeWiring = buildRuntimeWiring();
GraphQLSchema graphQLSchema = new SchemaGenerator().makeExecutableSchema(typeDefinitionRegistry, runtimeWiring);
graphQL = GraphQL.newGraphQL(graphQLSchema).build();
}
private void loadDataIntoHSQL() {
Stream.of(
new Book(“1001”, “The C Programming Language”, “PHI Learning”, “1978”,
new String[] {
“Brian W. Kernighan (Contributor)”,
“Dennis M. Ritchie”
}),
new Book(“1002”,”Your Guide To Scrivener”, “MakeUseOf.com”, “ April 21st 2013”,
new String[] {
“Nicole Dionisio (Goodreads Author)”
}),
new Book(“1003”,”Beyond the Inbox: The Power User Guide to Gmail”, “ Kindle Edition”, “November 19th 2012”,
new String[] {
“Shay Shaked”
, “Justin Pot”
, “Angela Randall (Goodreads Author)”
}),
new Book(“1004”,”Scratch 2.0 Programming”, “Smashwords Edition”, “February 5th 2015”,
new String[] {
“Denis Golikov (Goodreads Author)”
}),
new Book(“1005”,”Pro Git”, “by Apress (first published 2009)”, “2014”,
new String[] {
“Scott Chacon”
})
).forEach(book -> {
bookRepository.save(book);
});
}
private RuntimeWiring buildRuntimeWiring() {
return RuntimeWiring.newRuntimeWiring()
.type(“Query”, typeWiring -> typeWiring
.dataFetcher(“allBooks”, allBooksDataFetcher)
.dataFetcher(“book”, bookDataFetcher))
build();
}
public GraphQL getGraphQL(){
return graphQL;
}
}

當Spring Boot應用程序運行時,Spring框架將調用@PostConstruct方法。 @PostConstruct方法中的代碼會將書籍信息寫入HQL數據庫中。


在此服務類的buildRuntimeWiring()方法中,我們將兩個數據獲取程序進行運行時綁定:allBook和book。 此處定義的名稱allBookand book必須與我們已經創建的GraphQL文件中定義的類型匹配。

創建數據訪問層

GraphQL模式中的每種類型都有一個對應的數據提取器(data fetcher)。
我們需要為在架構中定義的allBooks和Book類型編寫兩個單獨的數據獲取器。


allBooks類型的數據獲取程器是這個。

package graphqlapp.service.datafetcher;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import graphqlapp.model.Book;
import graphqlapp.repository.BookRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class AllBooksDataFetcher implements DataFetcher<List<Book>> {
private BookRepository bookRepository;
@Autowired
public AllBooksDataFetcher(BookRepository bookRepository) {
this.bookRepository=bookRepository;
}
@Override
public List<Book> get(DataFetchingEnvironment dataFetchingEnvironment) {
return bookRepository.findAll();
}
}

Book類型的數據獲取器是這個。

package graphqlapp.service.datafetcher;
import graphql.schema.DataFetcher;
import graphqlapp.model.Book;
import graphqlapp.repository.BookRepository;
import graphql.schema.DataFetchingEnvironment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class BookDataFetcher implements DataFetcher<Book> {
private BookRepository bookRepository;
@Autowired
public BookDataFetcher(BookRepository bookRepository){
this.bookRepository = bookRepository;
}
@Override
public Book get(DataFetchingEnvironment dataFetchingEnvironment) {
String isn = dataFetchingEnvironment.getArgument(“id”);
return bookRepository.findById(isn).orElse(null);
}
}

運行應用

我在端口9002端口上運行此應用程序。 因此,我在application.properties文件中如下。

server.port=9002

這樣,我們的Spring Boot GraphQL應用程序就準備好了。 讓我們運行我們的Spring Boot應用程序,并使用Postman工具對其進行測試。


注意這里我們只有一個endpoinst,http://localhost:9002/rest/books


讓我們使用該單個endpoint查詢多個數據集。 為此,請打開Postman并在請求正文中添加以下查詢。

輸入1:我們要查詢一本ID為1001的特定書,并且只需要書名即可。 同時,我們正在查詢allBooks,并期望響應將包含is,title,author,publisher和publishedDate。

{
book(id:”1001"){
title
}

allBooks{
isn
title
author
publisher
publishedDate
}
}

輸出1:響應如下。

{
“errors”: [],
“data”: {
“book”: {
“title”: “The C Programming Language”
},
“allBooks”: [
{
“isn”: “1001”,
“title”: “The C Programming Language”,
“author”: [
“Brian W. Kernighan (Contributor)”,
“Dennis M. Ritchie”
],
“publisher”: “PHI Learning”,
“publishedDate”: “1978”
},
{
“isn”: “1002”,
“title”: “Your Guide To Scrivener”,
“author”: [
“Nicole Dionisio (Goodreads Author)”
],
“publisher”: “MakeUseOf.com”,
“publishedDate”: “ April 21st 2013”
},
{
“isn”: “1003”,
“title”: “Beyond the Inbox: The Power User Guide to Gmail”,
“author”: [
“Shay Shaked”,
“Justin Pot”,
“Angela Randall (Goodreads Author)”
],
“publisher”: “ Kindle Edition”,
“publishedDate”: “November 19th 2012”
},
{
“isn”: “1004”,
“title”: “Scratch 2.0 Programming”,
“author”: [
“Denis Golikov (Goodreads Author)”
],
“publisher”: “Smashwords Edition”,
“publishedDate”: “February 5th 2015”
},
{
“isn”: “1005”,
“title”: “Pro Git”,
“author”: [
“Scott Chacon”
],
“publisher”: “by Apress (first published 2009)”,
“publishedDate”: “2014”
}
]
},
“extensions”: null
}

輸入2:讓我們再次通過ID查詢特定圖書的標題和作者。

{
book(id:”1001"){
title
author
}
}

輸出2:輸出是這個。 我們獲得ID為1001的書的標題和作者。

{
“errors”: [],
“data”: {
“book”: {
“title”: “The C Programming Language”,
“author”: [
“Brian W. Kernighan (Contributor)”,
“Dennis M. Ritchie”
]
}
},
“extensions”: null
}

輸入3:讓我們查詢所有圖書的書名,作者,出版日期和出版商詳細信息

{
allBooks{
isn
title
author
publisher
publishedDate
}
}

輸出3:輸出是這個。

{
“errors”: [],
“data”: {
“allBooks”: [
{
“isn”: “1001”,
“title”: “The C Programming Language”,
“author”: [
“Brian W. Kernighan (Contributor)”,
“Dennis M. Ritchie”
],
“publisher”: “PHI Learning”,
“publishedDate”: “1978”
},
{
“isn”: “1002”,
“title”: “Your Guide To Scrivener”,
“author”: [
“Nicole Dionisio (Goodreads Author)”
],
“publisher”: “MakeUseOf.com”,
“publishedDate”: “ April 21st 2013”
},
{
“isn”: “1003”,
“title”: “Beyond the Inbox: The Power User Guide to Gmail”,
“author”: [
“Shay Shaked”,
“Justin Pot”,
“Angela Randall (Goodreads Author)”
],
“publisher”: “ Kindle Edition”,
“publishedDate”: “November 19th 2012”
},
{
“isn”: “1004”,
“title”: “Scratch 2.0 Programming”,
“author”: [
“Denis Golikov (Goodreads Author)”
],
“publisher”: “Smashwords Edition”,
“publishedDate”: “February 5th 2015”
},
{
“isn”: “1005”,
“title”: “Pro Git”,
“author”: [
“Scott Chacon”
],
“publisher”: “by Apress (first published 2009)”,
“publishedDate”: “2014”
}
]
},
“extensions”: null
}

在REST API上使用GraphQL的優勢就在于響應數據可以根據需求而變化,而不用返回一大堆無關的數據。

源代碼:
https://github.com/vinod827/acloudtiger-blog-posts

文章轉載自: 一種靈活的API設計模式:在Spring Boot中支持GraphQL

上一篇:

調整API策略應對AI動態趨勢

下一篇:

API開發完整指南
#你可能也喜歡這些API文章!

我們有何不同?

API服務商零注冊

多API并行試用

數據驅動選型,提升決策效率

查看全部API→
??

熱門場景實測,選對API

#AI文本生成大模型API

對比大模型API的內容創意新穎性、情感共鳴力、商業轉化潛力

25個渠道
一鍵對比試用API 限時免費

#AI深度推理大模型API

對比大模型API的邏輯推理準確性、分析深度、可視化建議合理性

10個渠道
一鍵對比試用API 限時免費