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 non-blocking JDBC connection, fully reactive and backpressure-aware.
2. Our First 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.