Google Cloud SQL for PostgreSQL + Spring Data JPA + Spring Boot - Build REST CRUD APIs

In this section, we will learn how to build REST CRUD APIs with Spring Boot, Spring Data JPA, and Google Cloud SQL for PostgreSQL.


1.  A little bit of Background

Spring Data JPA

Spring Data JPA aims to significantly improve the implementation of data access layers by reducing the effort to the amount that’s actually needed. As a developer you write your repository interfaces, including custom finder methods, and Spring will provide the implementation automatically.


Google Cloud SQL for PostgreSQL

Cloud SQL for PostgreSQL is a fully managed database service that helps you set up, maintain, manage, and administer your PostgreSQL relational databases on Google Cloud Platform.

Spring Boot

Spring Boot makes it easy to create stand-alone, production-grade Spring-based Applications that you can "just run". 


2. Create a GCP Project

First, Sign into the Google console at https://console.cloud.google.com.

You can create a new project by first selecting the project dropdown in the top left and selecting "New Project".


Next, specify your GCP Project name and Project ID.

Then Click on the "CREATE" button.

Copy "Project ID" and keep it for future purposes.


3. Create a Cloud SQL for PostgreSQL instance.

From cloud console, search for "SQL" like below and click on "SQL" button.


Next, click on "CREATE INSTANCE" button,

Then, click on "Choose PostgreSQL" button,

Then, enable the Compute Engine API by click on "ENABLE API" button.

After that, enter "Instance Id", "Password", "Choose a configuration to start with", "Region" etc...

Note "Password" and keep it safely for future purposes.

Then click on "CREATE INSTANCE" button,


Next, copy "Connection name" and keep it for future purposes.


4. Create a Database.

Click on "CREATE DATABASE" button,

Enter "Database name" and then click on "CREATE" button.

Copy "Database name" and keep it for future purposes.


5. Enable SQL admin API

From cloud console, search for "sqladmin API" like below and click on "sqladmin API" button.

Next, click on "ENABLE" button,


6. Create a service account key

First choose "IAM & Admin" and then click on "Service accounts".

After that, click on "CREATE SERVICE ACCOUNT".

Then, enter service account details like "Service account name", and "Service account ID" and click on "CREATE AND CONTINUE".

Then, grant basic role Editor.


Finally click on "DONE" button.

Then, from "Actions" click on "Manage keys".

Then, click on "Create new key".

Then, choose "Key type" as JSON and click on "CREATE" button.


Service account keys in JSON format will be download. Keep the file safe for future purposes.


7. Creating a simple spring boot web application

First, open the Spring initializr https://start.spring.io/ 

Then, Provide the Group and Artifact name. We have provided Group name com.knf.dev.demo and Artifact spring-boot-data-jpa-gcp-sql-postgresql-crud. Here I selected the Maven project - language Java 17 - Spring Boot 3.0.4 and add Spring web dependency, Spring Data JPAand GCP Support.

Then, click on the Generate button. When we click on the Generate button, it starts packing the project in a .zip(spring-boot-data-jpa-gcp-sql-postgresql-crud) file and downloads the project. Then, Extract the Zip file. 

Then, import the project on your favourite IDE.

Final Project directory:

Note: Place service account key JSON file in resources folder (Not recommended in production environment).


In the pom.xml, add CloudSQL Starter for PostgreSQL:

<!-- Add CloudSQL Starter for PostgreSQL -->
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>spring-cloud-gcp-starter-sql-postgresql</artifactId>
</dependency>


    Complete pom.xml

    <?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.0.4</version>
    <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.knf.dev.demo</groupId>
    <artifactId>spring-boot-data-jpa-gcp-sql-postgresql-crud</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-data-jpa-gcp-sql-postgresql-crud</name>
    <description>Demo project for Spring Boot</description>
    <properties>
    <java.version>17</java.version>
    <spring-cloud-gcp.version>4.1.1</spring-cloud-gcp.version>
    <spring-cloud.version>2022.0.1</spring-cloud.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>com.google.cloud</groupId>
    <artifactId>spring-cloud-gcp-starter</artifactId>
    </dependency>
    <!-- Add CloudSQL Starter for PostgreSQL -->
    <dependency>
    <groupId>com.google.cloud</groupId>
    <artifactId>spring-cloud-gcp-starter-sql-postgresql</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    </dependencies>
    <dependencyManagement>
    <dependencies>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>${spring-cloud.version}</version>
    <type>pom</type>
    <scope>import</scope>
    </dependency>
    <dependency>
    <groupId>com.google.cloud</groupId>
    <artifactId>spring-cloud-gcp-dependencies</artifactId>
    <version>${spring-cloud-gcp.version}</version>
    <type>pom</type>
    <scope>import</scope>
    </dependency>
    </dependencies>
    </dependencyManagement>

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

    </project>


    application.properties

    Add the following configuration to application.properties file:
    spring.cloud.gcp.sql.database-name=studentdb
    spring.cloud.gcp.sql.instance-connection-name=knf-gcp-demo-project-123:us-central1:knf-demo-instance
    spring.cloud.gcp.project-id=knf-gcp-demo-project-123
    spring.cloud.gcp.credentials.location=classpath:knf-gcp-demo-project-123-1862d50bc2fd.json

    # Leave empty for root, uncomment and fill out if you specified a user
    #spring.datasource.username=

    # Uncomment if root password is specified
    spring.datasource.password=YourPassword

    spring.jpa.hibernate.ddl-auto=update
    • Specify database name, instance name, project id, credential's location, and datasource password.
    • spring.jpa.hibernate.ddl-auto is used for database initialization. We set the value to update value so that a table will be created in the database automatically corresponding to defined data model. Any change to the model will also trigger an update to the table.

    Note: In this example, we placed service account key JSON file in resources folder (Not recommended in production environment).

    More secure way is place JSON in somewhere in server or docker image, then create environment variable "GOOGLE_APPLICATION_CREDENTIALS" and give the location to your JSON FILE.

    If your application is running on Google App Engine or Google Compute Engine, in most cases you should omit the "spring.cloud.gcp.credentials.location" property and instead, let Spring Cloud GCP Core Starter find the correct credentials for those environments.


    Create Student Entity - Student.java

    package com.knf.dev.demo.entity;

    import jakarta.persistence.*;

    @Entity
    @Table(name = "students")
    public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "first_name")
    private String firstName;

    @Column(name = "last_name")
    private String lastName;

    private String email;

    public Student() {

    }

    public Student(Long id, String firstName, String lastName, String email) {
    this.id = id;
    this.firstName = firstName;
    this.lastName = lastName;
    this.email = email;
    }

    public Long getId() {
    return id;
    }

    public void setId(Long id) {
    this.id = id;
    }

    public String getFirstName() {
    return firstName;
    }

    public void setFirstName(String firstName) {
    this.firstName = firstName;
    }

    public String getLastName() {
    return lastName;
    }

    public void setLastName(String lastName) {
    this.lastName = lastName;
    }

    public String getEmail() {
    return email;
    }

    public void setEmail(String email) {
    this.email = email;
    }
    }
    • The @Entity annotation specifies that the class is an entity and is mapped to a database table. 
    • The @Table annotation allows you to specify the details of the table that will be used to persist the entity in the database.
    • The @Id annotation specifies the primary key of an entity and the @GeneratedValue provides for the specification of generation strategies for the values of primary keys. 
    • The @Column annotation is used to specify the mapped column for a persistent property or field. If no Column annotation is specified, the default value will be applied.


    Create Student Repository - StudentRepository.java

    package com.knf.dev.demo.repository;

    import com.knf.dev.demo.entity.Student;
    import org.springframework.data.jpa.repository.JpaRepository;

    public interface StudentRepository extends JpaRepository<Student, Long> {

    }

    JpaRepository is JPA specific extension of Repository. It contains the full API of CrudRepository and PagingAndSortingRepository. So, it contains API for basic CRUD operations and also API for pagination and sorting.


    -Exception Handling

    Create custom Exception -ResourceNotFoundException.java

    package com.knf.dev.demo.exception;

    public class ResourceNotFoundException extends RuntimeException{

    private static final long serialVersionUID = 1L;

    public ResourceNotFoundException(String message) {
    super(message);
    }
    }


    Create Custom Error Response

    package com.knf.dev.demo.exception;

    import com.fasterxml.jackson.annotation.JsonFormat;
    import java.time.LocalDateTime;

    public class CustomErrorResponse {

    @JsonFormat(shape = JsonFormat.Shape.STRING,
    pattern = "yyyy-MM-dd hh:mm:ss")
    private LocalDateTime timestamp;
    private int status;
    private String error;
    public LocalDateTime getTimestamp() {
    return timestamp;
    }
    public void setTimestamp(LocalDateTime timestamp) {
    this.timestamp = timestamp;
    }
    public int getStatus() {
    return status;
    }
    public void setStatus(int status) {
    this.status = status;
    }
    public String getError() {
    return error;
    }
    public void setError(String error) {
    this.error = error;
    }
    }


    Create Global Exception Handler

    package com.knf.dev.demo.exception;

    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.context.request.WebRequest;
    import java.time.LocalDateTime;

    @ControllerAdvice
    public class GlobalExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<CustomErrorResponse> globalExceptionHandler
    (Exception ex, WebRequest request) {
    CustomErrorResponse errors = new CustomErrorResponse();
    errors.setTimestamp(LocalDateTime.now());
    errors.setError(ex.getMessage());
    errors.setStatus(HttpStatus.NOT_FOUND.value());
    return new ResponseEntity<>(errors, HttpStatus.NOT_FOUND);
    }
    }

    Spring supports exception handling by a Global Exception Handler (@ExceptionHandler) with Controller Advice (@ControllerAdvice). This enables a mechanism that makes ResponseEntity work with the type safety and flexibility of @ExceptionHandler.


    Create Student Controller

    package com.knf.dev.demo.controller;

    import com.knf.dev.demo.entity.Student;
    import com.knf.dev.demo.exception.ResourceNotFoundException;
    import com.knf.dev.demo.repository.StudentRepository;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;

    @RestController
    @RequestMapping("/api/v1")
    public class StudentController {

    @Autowired
    private StudentRepository studentRepository;

    // get all students
    @GetMapping("/students")
    public List<Student> getAllStudents()
    {
    return studentRepository.findAll();
    }

    // create student rest API
    @PostMapping("/students")
    public Student createStudent(@RequestBody Student student) {

    return studentRepository.save(student);

    }

    // get student by id rest api
    @GetMapping("/students/{id}")
    public Student findStudentById(@PathVariable Long id) {

    Student student = studentRepository.findById(id).
    orElseThrow(() -> new ResourceNotFoundException
    ("Student not exist with id :" + id));
    return student;
    }

    // update student rest api
    @PutMapping("/students/{id}")
    public Student updateStudent(@PathVariable Long id,
    @RequestBody Student studentDetails) {

    Student student = studentRepository.findById(id).
    orElseThrow(() -> new ResourceNotFoundException
    ("Student not exist with id :" + id));

    student.setFirstName(studentDetails.getFirstName());
    student.setLastName(studentDetails.getLastName());
    student.setEmail(studentDetails.getEmail());

    return studentRepository.save(student);
    }

    // delete student rest api
    @DeleteMapping("/students/{id}")
    public Map<String, Boolean> deleteStudent
    (@PathVariable Long id) {

    Student student = studentRepository.findById(id).
    orElseThrow(() -> new ResourceNotFoundException
    ("Student not exist with id :" + id));


    studentRepository.delete(student);

    Map<String, Boolean> response = new HashMap<>();
    response.put("deleted", Boolean.TRUE);
    return response;
    }
    }
    • Spring @RestController annotation is used to create RESTful web services using Spring MVC. Spring RestController takes care of mapping request data to the defined request handler method. Once response body is generated from the handler method, it converts it to JSON response.
    • @RequestMapping is used to map web requests onto specific handler classes and/or handler methods. @RequestMapping can be applied to the controller class as well as methods.
    • We can use the @Autowired to mark a dependency which Spring is going to resolve and inject.
    • @GetMapping annotation for mapping HTTP GET requests onto specific handler methods.
    • @PostMapping annotation for mapping HTTP POST requests onto specific handler methods.
    • @PutMapping annotation for mapping HTTP PUT requests onto specific handler methods.
    • @DeleteMapping annotation for mapping HTTP DELETE requests onto specific handler methods.
    • @RequestBody annotation is used to indicating a method parameter should be bind to the body of the HTTP request. Internally, this annotation uses HTTP Message converters to convert the body of HTTP requests to domain objects.
    • @PathVariable annotation used on a method argument to bind it to the value of a URI template variable.


    Run the application - Application.java

    package com.knf.dev.demo;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;

    @SpringBootApplication
    public class Application {

    public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
    }

    }

    Application is the entry point that sets up the Spring Boot application. The @SpringBootApplication annotation enables auto-configuration and component scanning.

    Step1: Download or clone the source code from GitHub to a local machine - Click here!


    Step 2: mvn clean install


    Step 3: Run the Spring Boot application - mvn spring-boot:run


    OR 


    Run this Spring boot application from 

    • IntelliJ IDEA IDE by right click - Run 'Application.main()' 
    • Eclipse/STS - You can right click the project or the Application.java file and run as java application or Spring boot application.

    Add Student:


    Update Student:


    Fetch all Student:


    Get Student by Id:


    Delete Student:

    Source code - click here!

    Popular posts from this blog

    Learn Java 8 streams with an example - print odd/even numbers from Array and List

    Java Stream API - How to convert List of objects to another List of objects using Java streams?

    Registration and Login with Spring Boot + Spring Security + Thymeleaf

    Java, Spring Boot Mini Project - Library Management System - Download

    ReactJS, Spring Boot JWT Authentication Example

    Top 5 Java ORM tools - 2024

    Java - Blowfish Encryption and decryption Example

    Spring boot video streaming example-HTML5

    Google Cloud Storage + Spring Boot - File Upload, Download, and Delete