Reactive APIs with Spring WebFlux, Spring Data JDBC, R2DBC and H2 - Part I

13/05/2019 - Arnaud Duforat

This post is an introduction to the development of reactive web applications with Spring WebFlux.

Spring 5 embraces Reactive Programming and and demonstrates it in its support for reactive access to databases or data stores like Cassandra, Couchbase, MongoDB or Redis. But there were still a lack in support for reactive access to relational databases. But it was before R2DBC (Reactive Relational Database Connectivity).

R2DBC, which is developed by Pivotal members as Spring, takes a normal JDBC driver and allows you to interact with it in a way that won’t block your application. It does this by scheduling potentially blocking work on different threads. Additionally, it has DSL that lets you model your SQL statements and results into streams. With this, we will see how simple it is to integrate our relational database with our reactive application.

We will also use Spring Data JDBC. It offers a repository abstraction based on JDBC and allow to access relational databases using Spring Data way (through CrudRepository interfaces) without including JPA library to the application dependencies. Spring Data JDBC aims to be much simpler conceptually than JPA by not implementing patterns like caching or lazy loading. It also provides only very limited support for annotation-based mapping. But unlike Spring Data JPA, it provides an implementation of reactive repositories that uses R2DBC for accessing relational database. Although that module is still under development (only SNAPSHOT version is available), we will use it in our demo application.

TLDR: The source code of the demo application is here: https://github.com/neokeld/spring-webflux-data-r2dbc-demo

Writing the build configuration with Maven

We include dependencies required for the demo implementation. We need to include the special SNAPSHOT version of Spring Data JDBC dedicated for accessing database using R2DBC. We also add R2DBC. As you can see below only Spring WebFlux is available in stable version (as part of Spring Boot RELEASE).

Warning: at the time of writing these lines, R2DBC is incompatible with H2 1.4.198+ so we set the version to 1.4.197 (see: https://github.com/r2dbc/r2dbc-h2/issues/51).

The resulting 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 http://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>2.1.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>fr.duforat.demos</groupId>
    <artifactId>dataflow</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>dataflow</name>
    <description>Data Flow project with Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jdbc</artifactId>
            <version>1.0.0.r2dbc-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>io.r2dbc</groupId>
            <artifactId>r2dbc-spi</artifactId>
            <version>1.0.0.M7</version>
        </dependency>
        <dependency>
            <groupId>io.r2dbc</groupId>
            <artifactId>r2dbc-h2</artifactId>
            <version>1.0.0.M7</version>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.4.197</version>
        </dependency>
        <!-- Scope test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <!-- Repo for r2dbc -->
    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>

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

</project>

Reactive Repositories

We are using well known Spring Data style of CRUD repository implementation. In that case we need to create interface that extends ReactiveCrudRepository interface.

Here’s the implementation of repository for managing DataSource objects:

@Repository
public interface DataSourceRepository extends ReactiveCrudRepository<DataSource, Long> {

    @Query("SELECT id, kind, source, title FROM datasource d")
    Flux<DataSource> findAll();
    
}

Implementing Entities

Here’s an implementation of DataSource entity class:

public class DataSource {

    @Id
    private long id;
    
    private String kind;
    private String source;
    private String title;
    
    public String getKind() {
        return kind;
    }
    public void setKind(String kind) {
        this.kind = kind;
    }
    public String getSource() {
        return source;
    }
    public void setSource(String source) {
        this.source = source;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
}

The SQL script schema.sql corresponding to the created entities is:

CREATE TABLE datasource(
id INTEGER PRIMARY KEY,
kind VARCHAR(64),
source VARCHAR(256),
title VARCHAR(256)
);

It will be automatically run to create a H2 database in memory with default username sa and no password. We don't have to make sure that ddl-auto (automatic initialization from entities definition) is disabled in our case because we don't anotate our entities and so our entities are only simple POJOs.

And building our sample web application

We need to enable Spring Data JDBC repositories by annotating the main class with @EnableJdbcRepositories.

@SpringBootApplication
@EnableJdbcRepositories
public class DataflowApplication {

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

}

Working with R2DBC requires to declare connection factory, client, and repository inside Spring @Configuration bean.

@Configuration
public class DataflowConfiguration {

    @Bean
    DataSourceRepository repository(R2dbcRepositoryFactory factory) {
        return factory.getRepository(DataSourceRepository.class);
    }
 
    @Bean
    R2dbcRepositoryFactory factory(DatabaseClient client) {
        RelationalMappingContext context = new RelationalMappingContext();
        context.afterPropertiesSet();
        return new R2dbcRepositoryFactory(client, context);
    }
 
    @Bean
    DatabaseClient databaseClient(ConnectionFactory factory) {
        return DatabaseClient.builder().connectionFactory(factory).build();
    }
 
    @Bean
    H2ConnectionFactory connectionFactory() {
        H2ConnectionConfiguration config = H2ConnectionConfiguration.builder()
                .username("sa")
                .password("")
                .url("mem:testdb")
                .build();
 
        return new H2ConnectionFactory(config);
    }
}

Finally, we can create a REST controller that contains the definition of our reactive API methods:

@RestController
@RequestMapping("/datasource")
public class DataSourceController {

    private DataSourceRepository dataSourceRepository;
    
    public DataSourceController(DataSourceRepository dataSourceRepository) {
        this.dataSourceRepository = dataSourceRepository;
    }
    
    @GetMapping
    public Flux<DataSource> findAll() {
        return dataSourceRepository.findAll();
    }
    
    public Mono<DataSource> add(@RequestBody DataSource datasource) {
        return dataSourceRepository.save(datasource);
    }
}

Finally you can call GET /datasource method using your web browser or curl. The result should be:

[
  {
    "kind": "csv",
    "source": "table.csv",
    "title": "Table"
  },
  {
    "kind": "json",
    "source": "test.json",
    "title": "Test"
  },
  {
    "kind": "r2dbc",
    "source": "mem:testdb",
    "title": "H2 connexion"
  }
]

To see another example with Kotlin, Postgresql and Docker, read the work of Piotr Mińkowski here: Introduction to Reactive APIs with Postgres, R2DBC, Spring Data JDBC and Spring WebFlux