A Quick Look at R2DBC with Spring Data

1. Introduction

R2DBC (Reactive Relational Database Connectivity) is an effort presented by Pivotal during Spring One Platform 2018. It intends to create a reactive API to SQL databases.

In other words, this effort creates a database connection using fully non-blocking drivers.

In this tutorial, we’ll have a look at an example of an application using Spring Data R2BDC. For a guide to the more low-level R2DBC API, have a look at our previous article.

2. Our First Spring Data R2DBC Project

To begin with, the R2DBC project is very recent. At this moment, only PostGres, MSSQL, and H2 have R2DBC drivers. Further, we can’t use all Spring Boot functionality with it. Therefore there’re some steps that we’ll need to manually add. But, we can leverage projects like Spring Data to help us.

We’ll create a Maven project first. At this point, there are a few dependency issues with R2DBC, so our pom.xml will be bigger than it’d normally be.

For the scope of this article, we’ll use H2 as our database and we’ll create reactive CRUD functions for our application.

Let’s open the pom.xml of the generated project and add the appropriate dependencies as well as some early-release Spring repositories:

<dependencies>
     <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>0.8.0.M8</version>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>1.4.199</version>
    </dependency>
</dependencies>

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

We need these repositories in order to use the milestone dependencies.

Other required artifacts include Lombok, Spring WebFlux and a few others that complete our project dependencies.

3. Connection Factory

When working with a database, we need a connection factory. So, of course, we’ll need the same thing with R2DBC.

So we’ll now add the details to connect to our instance:

@Configuration
@EnableR2dbcRepositories
class R2DBCConfiguration extends AbstractR2dbcConfiguration {
    @Bean
    public H2ConnectionFactory connectionFactory() {
        return new H2ConnectionFactory(
            H2ConnectionConfiguration.builder()
              .url("mem:testdb;DB_CLOSE_DELAY=-1;")
              .username("sa")
              .build()
        );
    }
}

The first thing we notice in the code above is the @EnableR2dbcRepositories. We need this annotation to use Spring Data functionality. In addition, we’re extending the AbstractR2dbcConfiguration since it’ll provide a lot of beans that we’d need later on.

4. Our First R2DBC Application

Our next step is to create the repository:

interface PlayerRepository extends ReactiveCrudRepository<Player, Integer> {}

The ReactiveCrudRepository interface is very useful. It provides, for example, basic CRUD functionality.

Finally, we’ll define our model class. We’ll use Lombok to avoid boilerplate code:

@Data
@NoArgsConstructor
@AllArgsConstructor
class Player {
    @Id
    Integer id;
    String name;
    Integer age;
}

5. Testing

It’s time to test our code. So, let’s start by creating a few test cases:

@Test
public void whenDeleteAll_then0IsExpected() {
    playerRepository.deleteAll()
      .as(StepVerifier::create)
      .expectNextCount(0)
      .verifyComplete();
}

@Test
public void whenInsert6_then6AreExpected() {
    insertPlayers();
    playerRepository.findAll()
      .as(StepVerifier::create)
      .expectNextCount(6)
      .verifyComplete();
}

6. Custom Queries

We can also generate custom queries. In order to add it, we’ll need to change our PlayerRepository:

@Query("select id, name, age from player where name = $1")
Flux<Player> findAllByName(String name);

@Query("select * from player where age = $1")
Flux<Player> findByAge(int age);

In addition to the existing tests, we’ll add tests to our recently updated repository:

@Test
public void whenSearchForCR7_then1IsExpected() {
    insertPlayers();
    playerRepository.findAllByName("CR7")
      .as(StepVerifier::create)
      .expectNextCount(1)
      .verifyComplete();
}

@Test
public void whenSearchFor32YearsOld_then2AreExpected() {
    insertPlayers();
    playerRepository.findByAge(32)
      .as(StepVerifier::create)
      .expectNextCount(2)
      .verifyComplete();
}

private void insertPlayers() {
    List<Player> players = Arrays.asList(
        new Player(1, "Kaka", 37),
        new Player(2, "Messi", 32),
        new Player(3, "Mbappé", 20),
        new Player(4, "CR7", 34),
        new Player(5, "Lewandowski", 30),
        new Player(6, "Cavani", 32)
    );
    playerRepository.saveAll(players).subscribe();
}

7. Batches

Another feature of R2DBC is to create batches. A batch is useful when executing multiple SQL statements as they’ll perform better than individual operations.

To create a Batch we need a Connection object:

Batch batch = connection.createBatch();

After our application creates the Batch instance, we can add as many SQL statements as we want. To execute it, we’ll invoke the execute() method. The result of a batch is a Publisher that’ll return a result object for each statement.

So let’s jump into the code and see how we can create a Batch:

@Test
public void whenBatchHas2Operations_then2AreExpected() {
    Mono.from(factory.create())
      .flatMapMany(connection -> Flux.from(connection
        .createBatch()
        .add("select * from player")
        .add("select * from player")
        .execute()))
      .as(StepVerifier::create)
      .expectNextCount(2)
      .verifyComplete();
}

8. Conclusion

To summarize, R2DBC is still in an early stage. It’s an attempt to create an SPI that will define a reactive API to SQL databases. When used with Spring WebFlux, R2DBC allows us to write an application that handles data asynchronously from the top and all the way down to the database.

As always the code is available at GitHub.

Leave a Reply

Your email address will not be published.