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=postgresschema.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
- mvn spring-boot:run
- http://localhost:8080/api/organizations
