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);
5. Implementing Pagination (Pageable)
Example: Reactive pagination
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();
}
}