webflux-postgresql-example-01

R2DBC

WebFlux (see: http://jreact.com/index.php/crud/webflux/)

About Mono and Flux see:
https://github.com/ZbCiok/reactor-mono-and-flux-examples
http://jreact.com/index.php/repository/project-reactor/

We will build a Spring WebFlux R2DBC example that makes CRUD Operations with PostgreSQL database – a Organization application in that:

  • Each Organization has id, name, description, status.
  • Organization APIs help to create, retrieve, update, delete Organizations.
  • Organization APIs also support custom finder methods such as find by status or by title.

Project structure

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.2</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<groupId>zjc-examples</groupId>
	<artifactId>webflux-postgresql-example-01</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>webflux-postgresql-example-01</name>
	<description>Spring Boot R2DBC and PostgreSQL example: CRUD Rest API</description>
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-r2dbc</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-webflux</artifactId>
		</dependency>

		<dependency>
			<groupId>org.postgresql</groupId>
			<artifactId>r2dbc-postgresql</artifactId>
			<scope>runtime</scope>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>io.projectreactor</groupId>
			<artifactId>reactor-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.properties

spring.r2dbc.url=r2dbc:postgresql://localhost:5432/reactivedb
spring.r2dbc.username=postgres
spring.r2dbc.password=postgres

schema.sql has SQL statement for automatic initializing database table (on spring-boot start).

DROP TABLE IF EXISTS organization;
CREATE TABLE IF NOT EXISTS organization (id SERIAL PRIMARY KEY, name VARCHAR(255), description VARCHAR(255), status BOOLEAN);
insert into organization(name, description, status) values('organization#1name', 'descr#1', 'true');
insert into organization(name, description, status) values('organization#2name', 'descr#2', 'true');
insert into organization(name, description, status) values('organization#3name', 'descr#3', 'true');

OrganizationController.java (Endpoints)

package zjc.examples.webflux.postgresql.controller;

import zjc.examples.webflux.postgresql.service.OrganizationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import zjc.examples.webflux.postgresql.model.Organization;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@CrossOrigin(origins = "http://localhost:8081")
@RestController
@RequestMapping("/api")
public class OrganizationController {
  @Autowired
  OrganizationService organizationService;
  
  @GetMapping("/organizations")
  @ResponseStatus(HttpStatus.OK)
  public Flux<Organization> getAllOrganizations(@RequestParam(required = false) String title) {
    if (title == null)
      return organizationService.findAll();
    else
      return organizationService.findByTitleContaining(title);
  }

  @GetMapping("/organizations/{id}")
  @ResponseStatus(HttpStatus.OK)
  public Mono<Organization> getOrganizationById(@PathVariable("id") int id) {
    return organizationService.findById(id);
  }

  @PostMapping("/organizations")
  @ResponseStatus(HttpStatus.CREATED)
  public Mono<Organization> createOrganization(@RequestBody Organization organization) {
    return organizationService.save(new Organization(organization.getName(), organization.getDescription(), false));
  }

  @PutMapping("/organizations/{id}")
  @ResponseStatus(HttpStatus.OK)
  public Mono<Organization> updateOrganization(@PathVariable("id") int id, @RequestBody Organization organization) {
    return organizationService.update(id, organization);
  }

  @DeleteMapping("/organizations/{id}")
  @ResponseStatus(HttpStatus.NO_CONTENT)
  public Mono<Void> deleteOrganization(@PathVariable("id") int id) {
    return organizationService.deleteById(id);
  }

  @DeleteMapping("/organizations")
  @ResponseStatus(HttpStatus.NO_CONTENT)
  public Mono<Void> deleteAllOrganizations() {
    return organizationService.deleteAll();
  }

  @GetMapping("/organizations/status")
  @ResponseStatus(HttpStatus.OK)
  public Flux<Organization> findByStatus() {
    return organizationService.findByStatus(true);
  }
}

Run

Code: https://github.com/ZbCiok/zjc-examples/tree/main/spring/webflux/webflux-postgresql-example-01

admin