Working with Reactive Data Access
1. Configuring R2DBC Data Source
Property
Description
spring.r2dbc.url
r2dbc:postgresql://host/db
spring.r2dbc.username/password
Credentials
spring.r2dbc.pool.initial-size/max-size
Pool tuning
spring.r2dbc.pool.max-idle-time
Idle eviction
spring.sql.init.mode
Init scripts: always/never
2. Creating Reactive Entities (@Table)
Example: R2DBC entity as record
@ Table ( "users" )
public record User (@ Id Long id, String email, Instant createdAt) {}
Note: R2DBC is NOT JPA — no relationships, lazy loading, or dirty tracking. Use
plain mappings.
3. Using ReactiveCrudRepository Interface
Example: Reactive repository with Mono and Flux
public interface UserRepo extends R2dbcRepository < User , Long > {
Mono< User > findByEmail (String email );
Flux< User > findByCreatedAtAfter (Instant since );
}
4. Writing Custom Reactive Queries (@Query)
Example: Reactive custom JPQL select and update
@ Query ( "SELECT * FROM orders WHERE customer_id = :cid AND total > :min" )
Flux < Order > highValue (@ Param ( "cid" ) Long cid, @ Param ( "min" ) BigDecimal min);
@ Modifying
@ Query ( "UPDATE orders SET status='CANCELLED' WHERE id = :id" )
Mono < Integer > cancel (@ Param ( "id" ) Long id);
Flux < User > page (Pageable p);
Mono < Page < User >> pageOf (Pageable p) { /* combine count + slice */ }
Element
Behavior
Pageable as parameter
Skip + limit applied
Page<T>
Use PageableExecutionUtils.getPage(...)
6. Using Database Client (DatabaseClient)
Example: Raw SQL query with DatabaseClient
Mono< User > user = client. sql ( "SELECT id, email FROM users WHERE id = :id" )
. bind ( "id" , 42L )
. map ((row, meta) -> new User (row. get ( "id" , Long.class), row. get ( "email" , String.class), null ))
. one ();
7. Handling Transactions (TransactionalOperator)
Example: Reactive transactional operator
@ Service
public class TransferService {
private final TransactionalOperator tx;
public Mono< Void > transfer (Long from , Long to , BigDecimal amt ) {
return debit (from, amt). then ( credit (to, amt)). as (tx :: transactional). then ();
}
}
// or
@ Transactional public Mono < Void > method () { /* … */ }
8. Implementing Reactive Specifications
Approach
Detail
Criteria API
org.springframework.data.relational.core.query.Criteria
R2dbcEntityTemplate
template.select(User.class).matching(Query.query(where("email").is(x)))
Specifications
Not supported (JPA-only feature)
9. Using Reactive Caching
Example: Reactive Redis cache-aside pattern
public Mono < User > byId (Long id) {
return reactiveRedis. opsForValue (). get ( "user:" + id)
. switchIfEmpty (repo. findById (id)
. flatMap (u -> reactiveRedis. opsForValue (). set ( "user:" + id, u, Duration. ofMinutes ( 10 )). thenReturn (u)));
}
Warning: Spring Cache annotations don't natively understand Mono/Flux — use
CacheMono / manual caching.
10. Testing Reactive Repositories (StepVerifier)
Example: Test reactive repo with StepVerifier
@ DataR2dbcTest
class UserRepoTests {
@ Autowired UserRepo repo;
@ Test void findsByEmail () {
repo. save ( new User ( null , "a@b" , Instant. now ())). block ();
StepVerifier. create (repo. findByEmail ( "a@b" ))
. expectNextMatches (u -> u. email (). equals ( "a@b" ))
. verifyComplete ();
}
}
11. Using R2DBC vs JDBC Comparison
JDBC + JPA
Blocking, mature ecosystem
Rich ORM (relations, lazy)
Connection-per-request
Best for traditional apps
R2DBC
Non-blocking I/O
Mapper-only, no relations
Higher concurrency at lower thread count
Best for high-fan-in services