Merge pull request #12869 from hkhan/JAVA-15377-update-graphql-spqr

[JAVA-15377] Update code for Graphql SPQR article
This commit is contained in:
Loredana Crusoveanu 2022-10-30 08:51:42 +02:00 committed by GitHub
commit 319cbe296c
20 changed files with 322 additions and 177 deletions

View File

@ -0,0 +1,3 @@
### Relevant Articles:
- [Getting Started With GraphQL SPQR and Spring Boot](https://www.baeldung.com/spring-boot-graphql-spqr)

View File

@ -0,0 +1,42 @@
<?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>
<artifactId>graphql-spqr-boot-starter</artifactId>
<version>1.0</version>
<name>graphql-spqr-boot-starter</name>
<parent>
<groupId>com.baeldung.graphql</groupId>
<artifactId>graphql-modules</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<dependencies>
<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>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.leangen.graphql</groupId>
<artifactId>graphql-spqr-spring-boot-starter</artifactId>
<version>${graphql-spqr-spring-boot-starter-version}</version>
</dependency>
</dependencies>
<properties>
<graphql-spqr-spring-boot-starter-version>0.0.6</graphql-spqr-spring-boot-starter-version>
</properties>
</project>

View File

@ -4,8 +4,8 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication @SpringBootApplication
public class SpringBootApp { public class SpqrBootStarterApp {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(SpringBootApp.class, args); SpringApplication.run(SpqrBootStarterApp.class, args);
} }
} }

View File

@ -1,4 +1,4 @@
package com.baeldung.sprq; package com.baeldung.spqr;
import java.util.Objects; import java.util.Objects;

View File

@ -0,0 +1,59 @@
package com.baeldung.spqr;
import io.leangen.graphql.annotations.GraphQLArgument;
import io.leangen.graphql.annotations.GraphQLMutation;
import io.leangen.graphql.annotations.GraphQLQuery;
import io.leangen.graphql.spqr.spring.annotations.GraphQLApi;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@Service
@GraphQLApi
public class BookService implements IBookService {
private static final Set<Book> BOOKS_DATA = initializeData();
@GraphQLQuery(name = "getBookWithTitle")
public Book getBookWithTitle(@GraphQLArgument(name = "title") String title) {
return BOOKS_DATA.stream()
.filter(book -> book.getTitle().equals(title))
.findFirst()
.orElse(null);
}
@GraphQLQuery(name = "getAllBooks", description = "Get all books")
public List<Book> getAllBooks() {
return new ArrayList<>(BOOKS_DATA);
}
@GraphQLMutation(name = "addBook")
public Book addBook(@GraphQLArgument(name = "newBook") Book book) {
BOOKS_DATA.add(book);
return book;
}
@GraphQLMutation(name = "updateBook")
public Book updateBook(@GraphQLArgument(name = "modifiedBook") Book book) {
BOOKS_DATA.removeIf(b -> Objects.equals(b.getId(), book.getId()));
BOOKS_DATA.add(book);
return book;
}
@GraphQLMutation(name = "deleteBook")
public boolean deleteBook(@GraphQLArgument(name = "book") Book book) {
return BOOKS_DATA.remove(book);
}
private static Set<Book> initializeData() {
Book book = new Book(1, "J.R.R. Tolkien", "The Lord of the Rings");
Set<Book> books = new HashSet<>();
books.add(book);
return books;
}
}

View File

@ -1,4 +1,4 @@
package com.baeldung.sprq; package com.baeldung.spqr;
import java.util.List; import java.util.List;
@ -12,4 +12,4 @@ public interface IBookService {
Book updateBook(Book book); Book updateBook(Book book);
boolean deleteBook(Book book); boolean deleteBook(Book book);
} }

View File

@ -0,0 +1,62 @@
package com.baeldung.spqr;
import com.baeldung.SpqrBootStarterApp;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Mono;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
@SpringBootTest(webEnvironment = RANDOM_PORT, classes = SpqrBootStarterApp.class)
class SpqrBootStarterAppIntegrationTest {
private static final String GRAPHQL_PATH = "/graphql";
@Autowired
private WebTestClient webTestClient;
@Test
void whenGetAllBooks_thenValidResponseReturned() {
String getAllBooksQuery = "{getAllBooks{ id title author }}";
webTestClient.post()
.uri(GRAPHQL_PATH)
.contentType(MediaType.APPLICATION_JSON)
.body(Mono.just(toJSON(getAllBooksQuery)), String.class)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.data.getAllBooks").isNotEmpty();
}
@Test
void whenAddBook_thenValidResponseReturned() {
String addBookMutation = "mutation { addBook(newBook: {id: 123, author: \"J. K. Rowling\", "
+ "title: \"Harry Potter and Philosopher's Stone\"}) { id author title } }";
webTestClient.post()
.uri(GRAPHQL_PATH)
.contentType(MediaType.APPLICATION_JSON)
.body(Mono.just(toJSON(addBookMutation)), String.class)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.data.addBook.id").isEqualTo("123")
.jsonPath("$.data.addBook.title").isEqualTo("Harry Potter and Philosopher's Stone")
.jsonPath("$.data.addBook.author").isEqualTo("J. K. Rowling");
}
private static String toJSON(String query) {
try {
return new JSONObject().put("query", query).toString();
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,13 @@
package com.baeldung.spqr;
import com.baeldung.SpqrBootStarterApp;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest(classes = SpqrBootStarterApp.class)
class SpringContextTest {
@Test
void whenSpringContextIsBootstrapped_thenNoExceptions() {
}
}

View File

@ -13,19 +13,6 @@
<version>1.0.0-SNAPSHOT</version> <version>1.0.0-SNAPSHOT</version>
</parent> </parent>
<dependencyManagement>
<dependencies>
<dependency>
<!-- Boot version compatible with spqr-boot-starter -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.6.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
@ -33,13 +20,24 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.leangen.graphql</groupId> <groupId>io.leangen.graphql</groupId>
<artifactId>graphql-spqr-spring-boot-starter</artifactId> <artifactId>spqr</artifactId>
<version>${graphql-spqr-spring-boot-starter-version}</version> <version>${spqr-version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<properties> <properties>
<graphql-spqr-spring-boot-starter-version>0.0.6</graphql-spqr-spring-boot-starter-version> <spqr-version>0.11.2</spqr-version>
</properties> </properties>
</project> </project>

View File

@ -1,4 +1,4 @@
package com.baeldung.sprq; package com.baeldung.spqr;
import io.leangen.graphql.annotations.GraphQLArgument; import io.leangen.graphql.annotations.GraphQLArgument;
import io.leangen.graphql.annotations.GraphQLMutation; import io.leangen.graphql.annotations.GraphQLMutation;

View File

@ -1,51 +1,53 @@
package com.baeldung.spqr; package com.baeldung.spqr;
import io.leangen.graphql.annotations.GraphQLArgument;
import io.leangen.graphql.annotations.GraphQLMutation;
import io.leangen.graphql.annotations.GraphQLQuery;
import io.leangen.graphql.spqr.spring.annotations.GraphQLApi;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
@Service @Service
@GraphQLApi
public class BookService implements IBookService { public class BookService implements IBookService {
Set<Book> books = new HashSet<>(); private static final Set<Book> BOOKS_DATA = initializeData();
@GraphQLQuery(name = "getBookWithTitle") @Override
public Book getBookWithTitle(@GraphQLArgument(name = "title") String title) { public Book getBookWithTitle(String title) {
return books.stream() return BOOKS_DATA.stream()
.filter(book -> book.getTitle() .filter(book -> book.getTitle().equals(title))
.equals(title))
.findFirst() .findFirst()
.orElse(null); .orElse(null);
} }
@GraphQLQuery(name = "getAllBooks", description = "Get all books") @Override
public List<Book> getAllBooks() { public List<Book> getAllBooks() {
return books.stream().collect(Collectors.toList()); return new ArrayList<>(BOOKS_DATA);
} }
@GraphQLMutation(name = "addBook") @Override
public Book addBook(@GraphQLArgument(name = "newBook") Book book) { public Book addBook(Book book) {
books.add(book); BOOKS_DATA.add(book);
return book; return book;
} }
@GraphQLMutation(name = "updateBook") @Override
public Book updateBook(@GraphQLArgument(name = "modifiedBook") Book book) { public Book updateBook(Book book) {
books.remove(book); BOOKS_DATA.removeIf(b -> Objects.equals(b.getId(), book.getId()));
books.add(book); BOOKS_DATA.add(book);
return book; return book;
} }
@GraphQLMutation(name = "deleteBook") @Override
public boolean deleteBook(@GraphQLArgument(name = "book") Book book) { public boolean deleteBook(Book book) {
return books.remove(book); return BOOKS_DATA.remove(book);
} }
}
private static Set<Book> initializeData() {
Book book = new Book(1, "J.R.R. Tolkien", "The Lord of the Rings");
Set<Book> books = new HashSet<>();
books.add(book);
return books;
}
}

View File

@ -1,4 +1,4 @@
package com.baeldung.sprq; package com.baeldung.spqr;
import graphql.ExecutionResult; import graphql.ExecutionResult;
import graphql.GraphQL; import graphql.GraphQL;
@ -21,9 +21,10 @@ public class GraphqlController {
@Autowired @Autowired
public GraphqlController(BookResolver bookResolver) { public GraphqlController(BookResolver bookResolver) {
GraphQLSchema schema = new GraphQLSchemaGenerator().withBasePackages("com.baeldung") GraphQLSchema schema = new GraphQLSchemaGenerator()
.withOperationsFromSingleton(bookResolver) .withBasePackages("com.baeldung")
.generate(); .withOperationsFromSingleton(bookResolver)
.generate();
this.graphQL = new GraphQL.Builder(schema).build(); this.graphQL = new GraphQL.Builder(schema).build();
} }

View File

@ -12,4 +12,4 @@ public interface IBookService {
Book updateBook(Book book); Book updateBook(Book book);
boolean deleteBook(Book book); boolean deleteBook(Book book);
} }

View File

@ -1,12 +1,9 @@
package com.baeldung.sprq; package com.baeldung.spqr;
import org.jobrunr.autoconfigure.JobRunrAutoConfiguration;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication @SpringBootApplication
@EnableAutoConfiguration(exclude = { JobRunrAutoConfiguration.class})
public class SpqrApp { public class SpqrApp {
public static void main(String[] args) { public static void main(String[] args) {

View File

@ -0,0 +1,61 @@
package com.baeldung.spqr;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Mono;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
@SpringBootTest(webEnvironment = RANDOM_PORT, classes = SpqrApp.class)
class SpqrAppIntegrationTest {
private static final String GRAPHQL_PATH = "/graphql";
@Autowired
private WebTestClient webTestClient;
@Test
void whenGetAllBooks_thenValidResponseReturned() {
String getAllBooksQuery = "{getAllBooks{ id title author }}";
webTestClient.post()
.uri(GRAPHQL_PATH)
.contentType(MediaType.APPLICATION_JSON)
.body(Mono.just(toJSON(getAllBooksQuery)), String.class)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.getAllBooks").isNotEmpty();
}
@Test
void whenAddBook_thenValidResponseReturned() {
String addBookMutation = "mutation { addBook(newBook: {id: 123, author: \"J. K. Rowling\", "
+ "title: \"Harry Potter and Philosopher's Stone\"}) { id author title } }";
webTestClient.post()
.uri(GRAPHQL_PATH)
.contentType(MediaType.APPLICATION_JSON)
.body(Mono.just(toJSON(addBookMutation)), String.class)
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.addBook.id").isEqualTo("123")
.jsonPath("$.addBook.title").isEqualTo("Harry Potter and Philosopher's Stone")
.jsonPath("$.addBook.author").isEqualTo("J. K. Rowling");
}
private static String toJSON(String query) {
try {
return new JSONObject().put("query", query).toString();
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,12 @@
package com.baeldung.spqr;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest(classes = SpqrApp.class)
class SpringContextTest {
@Test
void whenSpringContextIsBootstrapped_thenNoExceptions() {
}
}

View File

@ -11,15 +11,28 @@
<parent> <parent>
<groupId>com.baeldung</groupId> <groupId>com.baeldung</groupId>
<artifactId>parent-boot-2</artifactId> <artifactId>parent-modules</artifactId>
<version>0.0.1-SNAPSHOT</version> <version>1.0.0-SNAPSHOT</version>
<relativePath>../parent-boot-2</relativePath>
</parent> </parent>
<dependencyManagement>
<dependencies>
<dependency>
<!-- Boot version compatible with spqr-boot-starter -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.6.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<modules> <modules>
<module>graphql-dgs</module> <module>graphql-dgs</module>
<module>graphql-java</module> <module>graphql-java</module>
<module>graphql-spqr</module> <module>graphql-spqr</module>
<module>graphql-spqr-boot-starter</module>
</modules> </modules>
</project> </project>

View File

@ -66,11 +66,6 @@
<version>${awaitility.version}</version> <version>${awaitility.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>io.leangen.graphql</groupId>
<artifactId>spqr</artifactId>
<version>0.11.2</version>
</dependency>
<dependency> <dependency>
<groupId>org.reflections</groupId> <groupId>org.reflections</groupId>
<artifactId>reflections</artifactId> <artifactId>reflections</artifactId>

View File

@ -1,48 +0,0 @@
package com.baeldung.sprq;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@Service
public class BookService implements IBookService {
Set<Book> books = new HashSet<>();
@Override
public Book getBookWithTitle(String title) {
return books.stream()
.filter(book -> book.getTitle()
.equals(title))
.findFirst()
.orElse(null);
}
@Override
public List<Book> getAllBooks() {
return books.stream()
.collect(Collectors.toList());
}
@Override
public Book addBook(Book book) {
books.add(book);
return book;
}
@Override
public Book updateBook(Book book) {
books.remove(book);
books.add(book);
return book;
}
@Override
public boolean deleteBook(Book book) {
return books.remove(book);
}
}

View File

@ -1,65 +0,0 @@
package com.baeldung.sprq;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = SpqrApp.class)
@AutoConfigureMockMvc
public class GraphqlControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
BookService bookService;
private static final String GRAPHQL_PATH = "/graphql";
@Test
@Ignore("spqr lib is not compatible with Boot 2.7.x, related code needs be moved or upgraded to Boot 2.7.x")
public void givenNoBooks_whenReadAll_thenStatusIsOk() throws Exception {
String getAllBooksQuery = "{ getAllBooks {id author title } }";
this.mockMvc.perform(post(GRAPHQL_PATH).content(toJSON(getAllBooksQuery))
.contentType(
MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.getAllBooks").isEmpty());
}
@Test
@Ignore("spqr lib is not compatible with Boot 2.7.x, related code needs be moved or upgraded to Boot 2.7.x")
public void whenAddBook_thenStatusIsOk() throws Exception {
String addBookMutation = "mutation { addBook(newBook: {id: 123, author: \"J.R.R. Tolkien\", "
+ "title: \"The Lord of the Rings\"}) { id author title } }";
this.mockMvc.perform(post(GRAPHQL_PATH).content(toJSON(addBookMutation))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.addBook.id").value("123"))
.andExpect(jsonPath("$.addBook.author").value("J.R.R. Tolkien"))
.andExpect(jsonPath("$.addBook.title").value("The Lord of the Rings"));
}
private String toJSON(String query) throws JSONException {
JSONObject jsonObject = new JSONObject();
jsonObject.put("query", query);
return jsonObject.toString();
}
}