Wednesday, May 24, 2023

Spring Boot 3 Spring Data GraphQL CRUD

GraphQL is an open-source query language and runtime for APIs (Application Programming Interfaces). It was developed by Facebook and released in 2015. GraphQL provides a flexible and efficient way to define, query, and manipulate data in APIs.

At its core, GraphQL enables clients to request specific data from the server using a single API endpoint. Unlike traditional REST APIs that expose fixed endpoints with pre-defined data structures, GraphQL allows clients to specify exactly what data they need and receive it in a hierarchical structure. This reduces over-fetching and under-fetching of data, optimizing network usage and improving performance.

Key features of GraphQL include:

Strong typing: GraphQL uses a type system to define the shape of the data available in the API. This provides a clear contract between the server and clients, enabling better tooling, documentation, and validation.

Hierarchical queries: Clients can request data in a hierarchical structure, specifying nested fields and relationships. This allows clients to fetch related data in a single query, reducing the number of round trips to the server.

Efficient data fetching: GraphQL retrieves data from multiple resources in a single request, avoiding the need for multiple API calls. Clients have fine-grained control over the shape and size of the data they receive.

Mutations: GraphQL supports mutations for modifying data on the server. Clients can send requests to create, update, or delete data, using a similar syntax to queries.

Real-time updates: GraphQL supports subscriptions, allowing clients to receive real-time updates when data changes on the server. This is particularly useful for applications that require real-time features like chat or notifications.

GraphQL is language-agnostic, meaning it can be used with any programming language or framework. There are libraries and tools available for various programming languages to implement GraphQL servers and clients.

Overall, GraphQL provides a flexible and efficient approach to building APIs, empowering clients to request and receive precisely the data they need while reducing network overhead.






HSQLDB (HyperSQL Database) is an open-source, in-memory relational database management system (RDBMS). It is written in Java and provides a lightweight and fast database solution for applications. HSQLDB is often used in Java-based projects, including web applications and testing environments.


Technology

  • Spring Boot 3.0.6
  • HyperSQL
  • Maven 
  • IntelliJ IDEA

Configuration Spring Boot project  :

<?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>3.1.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.henry</groupId>
<artifactId>spring-for-graphql</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-for-graphql</name>
<description>Demo Spring for GraphQL</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-graphql</artifactId>
</dependency>
<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>
<!-- https://mvnrepository.com/artifact/com.graphql-java/graphql-java-tools -->
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-tools</artifactId>
<version>5.2.4</version>
</dependency>

<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.graphql</groupId>
<artifactId>spring-graphql-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>


application.yml:


# HSQLDB Configuration
spring:
datasource:
url: jdbc:hsqldb:mem:testdb
username: sa
password:
driver-class-name: org.hsqldb.jdbc.JDBCDriver

# Hibernate Configuration
jpa:
database-platform: org.hibernate.dialect.HSQLDialect
hibernate.ddl-auto: create-drop

# graphql Configuration
graphql:
graphiql:
enabled: true

logging:
level:
org:
hibernate:
SQL: DEBUG


Schema

Spring for GraphQL application, create a directory src/main/resources/graphql. Add a new file schema.graphqls to this folder with the following content:

type Query {
getAllBooks: [Book]
getAllAuthors: [Author]
getBookById(id: ID!): Book
getAuthorById(id: ID!): Author
}

type Mutation {
createAuthor(firstName: String!, lastName: String! ): Author
updateAuthor(id: ID!, firstName: String!, lastName: String!): Author
deleteAuthor(id: ID!): Boolean
createBook(title: String!, authorId: ID!): Book
updateBook(id: ID!, title: String!): Book
deleteBook(id: ID!): Boolean
}

type Book {
id: ID
title: String!
author: Author!
}

type Author {
id: ID
firstName: String!
lastName: String!
bookRecords: [Book]
}



Spring Data GraphQL Application


Model


package com.henry.model;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.Set;

@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity
@Table(name = "author")
public class Author implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", unique = true, nullable = false)
private Long id;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;

@OneToMany(fetch = FetchType.LAZY, mappedBy = "author")
private Set<Book> bookRecords;


}


package com.henry.model;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity
@Table(name = "book")
public class Book implements Serializable {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "author_id", nullable = false)
private Author author;
}


Repository



package com.henry.repository;

import com.henry.model.Author;
import org.springframework.data.jpa.repository.JpaRepository;

public interface AuthorRepository extends JpaRepository<Author, Long> {

}

package com.henry.repository;

import com.henry.model.Book;
import org.springframework.data.jpa.repository.JpaRepository;

public interface BookRepository extends JpaRepository<Book,Long> {
}


Resolver


package com.henry.resolver;

import com.coxautodev.graphql.tools.GraphQLMutationResolver;
import com.coxautodev.graphql.tools.GraphQLQueryResolver;
import com.henry.model.Author;
import com.henry.model.Book;
import com.henry.repository.AuthorRepository;
import com.henry.repository.BookRepository;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class QueryAndMutationResolver implements GraphQLQueryResolver, GraphQLMutationResolver {

private final BookRepository bookRepository;
private final AuthorRepository authorRepository;


public QueryAndMutationResolver(BookRepository bookRepository, AuthorRepository authorRepository) {
this.bookRepository = bookRepository;
this.authorRepository = authorRepository;
}

public List<Book> getAllBooks() {
return bookRepository.findAll();
}

public Book getBookById(Long id) {
return bookRepository.findById(id).orElse(null);
}

public Iterable<Author> getAllAuthors() {
return authorRepository.findAll();
}

public Author getAuthorById(Long id) {
return authorRepository.findById(id).orElseGet(null);
}

public Author createAuthor(String firstName, String lastName ) {
Author author = new Author();
author.setFirstName(firstName);
author.setLastName(lastName);
return authorRepository.saveAndFlush(author);
}

public Author updateAuthor(Long id, String firstName, String lastName) {
Author author = authorRepository.findById(id).orElse(null);
if (author != null) {
author.setFirstName(firstName);
author.setLastName(lastName);
return authorRepository.saveAndFlush(author);
}
return null;
}

public Boolean deleteAuthor(Long id) {
authorRepository.deleteById(id);
return true;
}

public Book createBook(String title, Long author_id) {

Author author = authorRepository.findById(author_id).
orElseGet(null);

Book book = new Book();
book.setTitle(title);
book.setAuthor(author);
return bookRepository.saveAndFlush(book);
}

public Book updateBook(Long id, String title) {
Book book = bookRepository.findById(id).orElse(null);
if (book != null) {
book.setTitle(title);
return bookRepository.saveAndFlush(book);
}
return null;
}

public Boolean deleteBook(Long id) {
bookRepository.deleteById(id);
return true;
}
}




Controller


package com.henry.controller;

import com.henry.model.Author;
import com.henry.resolver.QueryAndMutationResolver;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.data.method.annotation.SchemaMapping;
import org.springframework.stereotype.Controller;

@Controller
public class AuthorController {
private final QueryAndMutationResolver queryAndMutationResolver;

public AuthorController(QueryAndMutationResolver queryAndMutationResolver) {
this.queryAndMutationResolver = queryAndMutationResolver;
}

@QueryMapping
public Author getAuthorById(@Argument Long id) {
return queryAndMutationResolver.getAuthorById(id);
}

@QueryMapping
public Iterable<Author> getAllAuthors(){
return queryAndMutationResolver.getAllAuthors();
}

@SchemaMapping(typeName = "Mutation", field = "createAuthor")
public Author createAuthor(@Argument String firstName, @Argument String lastName){
return queryAndMutationResolver.createAuthor(firstName, lastName);
}

@SchemaMapping(typeName = "Mutation", field = "updateAuthor")
public Author updateAuthor(@Argument Long id, @Argument String firstName, @Argument String lastName) {
return queryAndMutationResolver.updateAuthor(id,firstName, lastName);
}

@SchemaMapping(typeName = "Mutation", field = "deleteAuthor")
public Boolean deleteAuthor(@Argument Long id) {
return queryAndMutationResolver.deleteAuthor(id);
}
}


package com.henry.controller;

import com.henry.model.Book;
import com.henry.resolver.QueryAndMutationResolver;
import org.springframework.graphql.data.method.annotation.Argument;
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 BookController {

private final QueryAndMutationResolver queryAndMutationResolver;

public BookController(QueryAndMutationResolver queryAndMutationResolver) {
this.queryAndMutationResolver = queryAndMutationResolver;
}


@QueryMapping
public Book getBookById(@Argument Long id) {
return queryAndMutationResolver.getBookById(id);
}

@QueryMapping
public List<Book> getAllBooks(){
return queryAndMutationResolver.getAllBooks();
}

@SchemaMapping(typeName = "Mutation", field = "createBook")
public Book createBook(@Argument String title, @Argument Long authorId){
return queryAndMutationResolver.createBook(title, authorId);
}

@SchemaMapping(typeName = "Mutation", field = "updateBook")
public Book updateBook(@Argument Long id, @Argument String title) {
return queryAndMutationResolver.updateBook(id,title);
}

@SchemaMapping(typeName = "Mutation", field = "deleteBook")
public Boolean deleteBook(@Argument Long id) {
return queryAndMutationResolver.deleteBook(id);
}
}


Run & Test

Run Spring Boot application with command: mvn spring-boot:run. by console, IntelliJ etc.

Boot the application

Start your Spring application. Navigate to http://localhost:8080/graphiql.


Run the queries

Type in the queries and click the play button at the top of the window.












Or Postman

POST
http://localhost:8080/graphql

















Author queries

mutation { createAuthor( firstName: "The Author Title", lastName: "test" ) { id firstName lastName } } mutation { updateAuthor( id: 3, firstName: "Test1", lastName: "test123" ) { id firstName lastName } } mutation { deleteAuthor( id: 3 ) } query { getAuthorById(id: 1) { firstName lastName bookRecords { id } } } query { getAllAuthors { id firstName lastName bookRecords { id } } }


Book queries

mutation {
  createBook(
    title: "The Book Title", 
    authorId: 1
   ) 
  {
    id
    title
    author {
      id
      firstName
      lastName
    }
  }
}

mutation {
  updateBook( 
    id: 2,
    title: "Test1"
   ) 
  {
    id
    title
    author {
      id
      firstName
      lastName
    }
  }
}

mutation {
  deleteBook( 
    id: 3
   ) 
}

query  {
  getBookById(id: 1) {
    title
    author {
      id
      firstName
      lastName
    }
  }
}

query {
  getAllBooks
  {
    id
    title
    author {
      id
      firstName
      lastName
    }
  }
}



Source Code


Here on GitHub.











No comments:

Post a Comment

Provisioning Cloud SQL with Private Service Connect Using Terraform & Accessing from Cloud Run with Spring Boot

In this post, we'll explore how to provision Cloud SQL instances with Private Service Connect (PSC) connectivity using Terraform and the...