The following article shows different ways to access data using r2dbc in a Spring Boot application. r2dbc does not depend on Spring, it just depends on Java 8 and Reactive Streams.
A lot has been going on concerning reactive libraries lately. The last SpringOne Platform features talks about spring WebFlux and a reactive approach towards SQL relational databases. Thing is: a lot of applications rely on relational data-stores. JDBC in itself is blocking. If you use a JDBC driver in a Spring WebFlux application, then the database access is still blocking. There is no benefit from non-blocking calls and back-pressure.
r2dbc solves this problem, as it adds non-blocking relational database access. Note: r2dbc is in development, currently on M8 (milestone 8) and there is no public release date. It should either influence the development of ADBA or be might have a RELEASE’d version in the future.
I identified three different ways to use r2dbc:
- SPI (Service Provider Interface)
- r2dbc-client
- spring-data-r2dbc
Example for the r2dbc SPI implementation
@Repository @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE) public class SpringR2dbcFetchVehiclesGateway implements FetchVehiclesOutPort { @NonNull ConnectionFactory connectionFactory; @Override public Flux<Vehicle> observe() { return connection() .flatMapMany(connection -> Flux.from(connection.createStatement("select * from vehicle").execute()) .flatMap(res -> res.map((row, rowMetadata) -> Vehicle.builder() .brand(row.get("brand", String.class)) .vehicleId(row.get("vehicleId", Long.class)) .build()))); } private Mono<Connection> connection() { return Mono.from(connectionFactory.create()); } }
ConnectionFactory is a bean in the project. The private method connection() is returning a connection of the connectionFactory. Invoking “observe()” will load vehicles from the database. To keep it simple, the “Vehicle” entity contains two fields: brand and vehicleId. The first two lines of the method shows that the statement “select * from vehicle” is executed. This returns a Flux. The line after uses the result and maps every row of the result to an object of the vehicle entity. If you are used to JDBCTemplates and the row mapper this should look familiar.
Example with r2bdc client
@Repository @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE) public class SpringR2dbcFetchVehiclesGateway implements FetchVehiclesOutPort { @NonNull R2dbc r2dbc; @Override public Flux<Vehicle> observe() { return r2dbc.withHandle(handle -> handle.select("select * from vehicle") .mapResult(res -> res.map((row, rowMetadata) -> Vehicle.builder() .brand(row.get("brand", String.class)) .vehicleId(row.get("vehicleId", Long.class)) .build()))); } }
R2dbc is the client implementation and added as a bean. Instead of having a connectionFactory and issuing connections, r2dbc-client is a wrapper for this implementation. By using the “handle” function I can run select, executes and also open transactions. The row mapper is the same as the previous example.
Example with spring-data-r2bdc
public interface VehicleRepository extends ReactiveCrudRepository<Vehicle, Long> { }
Way simpler, spring-data takes care of the object-relation mapping. ReactiveCrudRepository offers methods like “findAll”, so there is no need to implement them manually.
Most probably I am going to use the client version. It is in my opinion similar to the jdbcTemplate which I am used to. But all different variants achieve more or less the same!