Spring Data R2DBC with PostgreSQL Database on Amazon RDS

In this post, we’ll implement a sample REST API that uses Spring Data R2DBC with PostgreSQL Database on Amazon Relational Database Service.

Overview

The Reactive Relational Database Connectivity (R2DBC) project brings reactive programming APIs to relational databases. It defines a non-blocking SPI for database driver implementors and client library authors. R2DBC drivers fully implement the database wire protocol on top of a non-blocking I/O layer.

Creating an RDS PostgreSQL Instance

Amazon Relational Database Service (Amazon RDS) makes it easy to set up, operate, and scale a relational database in the cloud. It provides cost-efficient and resizable capacity while automating time-consuming administration tasks, such as hardware provisioning, database setup, patching, and backups. It frees you to focus on your applications so you can give them the fast performance, high availability, security, and compatibility they need. — https://aws.amazon.com/rds/

  1. Log in to the AWS Management Console and open the Amazon RDS service.
  2. Create a PostgreSQL DB instance on the right button.
Engine options, Template sections
Settings, DB instance sections
Database Connectivity section

3. Configure Security Group for PostgreSQL DB instance

We want to connect to our instance outside the AWS environment. By default, the instance’s VPC security group does not allow any inbound connections. We need to add an inbound rule on the instance security group VPC

4. Connecting to the PostgreSQL DB instance

Open pgAdmin4 then connect to our instance.

Now, we’ll create the first database “book-rdbc-db

Spring Boot App Setup

We will start by creating a simple Spring Boot project from start.spring.io, with the following dependencies: Spring Reactive Web, Spring Data R2DBC, Spring Data JPA, Liquibase Migration, PostgreSQL Driver, Spring Data JDBC, R2DBC driver, and Lombok.

Spring Data R2DBC supports drivers through R2DBC’s pluggable SPI mechanism. It reacts to database specifics by inspecting the ConnectionFactory and selecting the appropriate database dialect.

Database initialization

The Spring Data reactive driver requires a configuration to connect to the PostgresDB. We can either add our database connection details in application.yml.

rds:
  db:
    url: r2dbc-instance.ceox6edjpuog.us-east-2.rds.amazonaws.com/book-rdbc-db
    username: postgres
    password: postgres

spring:
  liquibase:
    url: jdbc:postgresql://${rds.db.url}
    user: ${rds.db.username}
    password: ${rds.db.password}
    change-log: classpath:changelog/changelog-master.xml

  r2dbc:
    url: r2dbc:postgresql://${rds.db.url}
    username:  ${rds.db.username}
    password: ${rds.db.password}

We added liquibase configurations to create the database schema and insert data before use by Spring Data R2DBC.

We will be creating a new changelog that creates a new column. Here is our db.changelog-1.0.xml.

<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
                    http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">

    <changeSet id="202010211812" author="aek">

        <createTable tableName="book">
            <column name="id" type="bigint">
                <constraints primaryKey="true" primaryKeyName="book_id_pk" />
            </column>
            <column name="title" type="varchar(100)">
                <constraints unique="true" uniqueConstraintName="book_title_unq" />
            </column>
            <column name="isbn" type="varchar(255)"/>
            <column name="description" type="varchar(255)"/>
            <column  name="page"  type="int"/>
            <column  name="price"  type="double"/>
        </createTable>

        <createTable tableName="author">
            <column name="id" type="bigint">
                <constraints primaryKey="true" primaryKeyName="author_id_pk" />
            </column>
            <column name="lastname" type="varchar(255)">
                <constraints nullable="false"/>
            </column>
            <column name="firstName" type="varchar(255)"/>
        </createTable>

        <addAutoIncrement tableName="book" columnName="id" columnDataType="bigint" startWith="1" incrementBy="1" />
        <addAutoIncrement tableName="author" columnName="id" columnDataType="bigint" startWith="1" incrementBy="1" />
    </changeSet>


    <changeSet id="202010211812-1-data" author="aek" context="faker">
        <loadData
                file="changelog/data/book.csv"
                separator=","
                tableName="book"
                usePreparedStatements="true">
            <column name="title" type="string"/>
            <column name="page" type="numeric"/>
            <column name="isbn" type="string"/>
            <column name="description" type="string"/>
            <column name="price" type="numeric"/>
        </loadData>
    </changeSet>
</databaseChangeLog>

Let’s get to the code

Next, add the Java code that will use R2DBC to store and retrieve data from your PostgreSQL database.

Our entity class to represent the Book:

Book.java
  • The id of an entity should be annotated with Spring Data’s @Id annotation.
  • Do note that we can not add @Entity (It’s not JPA)
  • The @Table annotation is optional.
  • Lombok is recommended to be used to avoid boilerplate code.

To manage that class, you’ll need a repository.

BookRepository.java

To configure R2DBC repositories, we use the @EnableR2dbcRepositories annotation in the Spring boot launcher class.

Complete the implementation by creating the controller.

@RestController
@RequestMapping("/api/book")
public class BookController {

    private final BookRepository bookRepository;

    public BookController(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Mono<Book> createBook(@RequestBody Book book) {
        return bookRepository.save(book);
    }

    @GetMapping
    public Flux<Book> getBooks() {
        return bookRepository.findAll();
    }


    @GetMapping("/{bookId}")
    public Mono<ResponseEntity<Book>> getBookById(@PathVariable long bookId){
        return bookRepository.findById(bookId)
                .map(ResponseEntity::ok)
                .defaultIfEmpty(ResponseEntity.notFound().build());
    }

    @PutMapping("{bookId}")
    public Mono<ResponseEntity<Book>> updateBook(@PathVariable long bookId, @RequestBody Mono<Book> bookMono){
        return bookRepository.findById(bookId)
                .flatMap(book -> bookMono.map(u -> {
                    book.setDescription(u.getDescription());
                    book.setPrice(u.getPrice());
                    book.setIsbn(u.getIsbn());
                    book.setPrice(u.getPrice());
                    book.setPage(u.getPage());
                    return book;
                }))
                .flatMap(bookRepository::save)
                .map(ResponseEntity::ok)
                .defaultIfEmpty(ResponseEntity.notFound().build());
    }

    @DeleteMapping("/{id}")
    public Mono<ResponseEntity<Void>> deleteBook(@PathVariable long id) {
        return bookRepository.findById(id)
                .flatMap(s ->
                        bookRepository.delete(s)
                                .then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK)))
                )
                .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
    }

}

Test the REST API

  • GET all Book
get all book postman result
  • Create new Book
create new Book Postman result

Congratulations 😊!! Our REST application with Spring Data R2DBC is ready.

Conclusion

In this story, we implemented a REST API application that uses Spring Data R2DBC with PostgreSQL Database on Amazon Relational Database Service.

Although Spring Data R2DBC has advantages. It is not a full ORM like JPA and does not currently support relationships. So developers have to put more manual effort to perform join actions.

The complete source code is available on GitHub.

References

👉 Link to Medium blog

Related Posts