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

16/05/19 - Arnaud Duforat

The second milestone of spring-data-r2dbc came out two days ago and on this occasion, I come back to my previous article to use it and observe what it changes.

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

Previous article: https://blog.dema.in/flux1.html

The first and main thing that has to change is the dependencies:

<?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.2.0.M3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>fr.duforat.demos</groupId>
    <artifactId>dataflow-next</artifactId>
    <version>0.2.0-SNAPSHOT</version>
    <name>dataflow-next</name>
    <description>Data Flow project with Spring Data R2DBC</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-r2dbc</artifactId>
            <version>1.0.0.M2</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>

    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/libs-milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

</project>

Here we see that we have replaced the duo spring-data-jdbc / r2dbc-spi by spring-data-r2dbc. Thanks to this we no longer have libraries in spapshot so we remove the Spring Snapshots repository.

As explained in the issue https://github.com/spring-projects/spring-data-r2dbc/issues/40 spring.datasource properties are not supported by Spring Data R2DBC but will be certainly supported by an external autoconfiguration project in the future. The consequence is that we can't load a schema.sql or data.sql. To create our table and load some initial data without polluting our main class or configuration class, we can add an independant DataInitialization class which is going to be based on the use of and @EventListener to run some SQL at Spring Boot application start. data-r2dbc provides a database client to do it:

@Component
public class DataInitialization {

    private final DatabaseClient databaseClient;

    public DataInitialization(DatabaseClient databaseClient) {
        this.databaseClient = databaseClient;
    }

    @EventListener(ApplicationReadyEvent.class)
    public void init() {
        this.databaseClient.execute()
        .sql("CREATE TABLE DATASOURCE (\r\n" + 
                "id INTEGER PRIMARY KEY,\r\n" + 
                "kind VARCHAR(64),\r\n" + 
                "source VARCHAR(256),\r\n" + 
                "title VARCHAR(256)\r\n" + 
                ");")
        .fetch()
        .all()
        .log()
        .subscribe();
        
        this.databaseClient.execute()
        .sql("INSERT INTO DATASOURCE (id, kind, source, title) VALUES\r\n" + 
                "(1, 'csv', 'table.csv', 'Table'),\r\n" + 
                "(2, 'json', 'test.json', 'Test'),\r\n" + 
                "(3, 'r2dbc', 'mem:testdb', 'H2 connexion');")
        .fetch()
        .all()
        .log()
        .subscribe();
    }
}

From there will come forth the next changes: the configuration. Spring Data R2DBC initializes the repository, factory and databaseClient for us in AbstractR2dbcConfiguration so we only have to extend it and configure the connectionFactory:

@Configuration
@EnableR2dbcRepositories
public class DataflowConfiguration extends AbstractR2dbcConfiguration {

    @Bean
    public H2ConnectionFactory connectionFactory() {
        H2ConnectionConfiguration config = H2ConnectionConfiguration.builder()
                .username("sa")
                .password("")
                .url("mem:testdb;DB_CLOSE_DELAY=-1")
                .build();
 
        return new H2ConnectionFactory(config);
    }
}

We also have added DB_CLOSE_DELAY=-1. data-r2dbc closes connection with h2 after each query and with an h2 in memory, h2 delete the content of the database after the last connection is closed. So we have chosen to maintain h2 database up as long as the vm is up.

So with this article, we have shown that data-r2dbc makes it easy to implement R2DBC based repositories, by drastically reducing the amount of code to produce, in particular by reducing the necessary configuration.