Spring Data R2DBC provides repository-based persistence for relational databases through R2DBC. Unlike JDBC, R2DBC uses Reactive Streams for nonblocking data access. Spring Data R2DBC supplies repository support similar to Spring Data JDBC, but its handling of relationships calls for a different domain design. section 13.1 section 13.4
To start a relational persistence project:
- Add the spring-boot-starter-data-r2dbc dependency, or select Spring Data R2DBC in Spring Initializr. section 13.1
- Add a relational database and its matching R2DBC driver. An in-memory H2 setup uses both the H2 database library and the H2 R2DBC driver. section 13.1
- Define entities and matching tables. Give a new entity a database-generated Long ID: a non-null ID is treated as an update when a repository saves it. section 13.1.1
Consider ingredients, tacos, and orders. Their entities need setters for persisted properties. Direct entity relationships are not supported here, so a taco stores ingredient IDs in a Set<Long> rather than a collection of Ingredient objects. Orders likewise store taco IDs. Persisting a taco as part of an order aggregate requires more work than it does with Spring Data JDBC. section 13.1.1 section 13.1.2
Listing 13.2 The Taco entity class for R2DBC persistence
package tacos;
import java.util.HashSet;
import java.util.Set;
import org.springframework.data.annotation.Id;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
@Data
@NoArgsConstructor
@RequiredArgsConstructor
public class Taco {
@Id
private Long id;
private @NonNull String name;
private Set<Long> ingredientIds = new HashSet<>();
public void addIngredient(Ingredient ingredient) {
ingredientIds.add(ingredient.getId());
}
}
The Taco entity shows the practical workaround: ingredientIds holds unique database IDs, and addIngredient copies an ingredient’s ID into that set. section 13.1.1
Create an interface extending ReactiveCrudRepository for each entity. Repository methods return reactive types: findAll() returns a Flux of orders, while a derived finder such as findBySlug(String slug) returns a Mono of one ingredient. Subscribe to a Flux to start its flow of data. section 13.1.2
Listing 13.6 Persisting Ingredient objects with a reactive repository
package tacos.data;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import reactor.core.publisher.Mono;
import tacos.Ingredient;
public interface IngredientRepository
extends ReactiveCrudRepository<Ingredient, Long> {
Mono<Ingredient> findBySlug(String slug);
}
IngredientRepository shows both the ReactiveCrudRepository declaration and a slug-based finder returning Mono<Ingredient>. section 13.1.2
Test a generated repository
Annotate an integration test with @DataR2dbcTest to make generated repositories available for injection. Save test ingredients, then use StepVerifier to check how many were saved, inspect the items returned by findAll(), and verify that findBySlug("FLTO") emits the expected flour-tortilla ingredient. StepVerifier subscribes to the publisher as it checks it. section 13.1.3
