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.

2. Our First Spring Data R2DBC Project

<dependencies>
     <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-r2dbc</artifactId>
    </dependency>
    <dependency>
        <groupId>io.r2dbc</groupId>
        <artifactId>r2dbc-h2</artifactId>
    </dependency>
</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 makes it easier to build Spring-powered applications that use relational data access technologies in a reactive application stack. 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.