1. Overview
In this tutorial, we'll learn how to configure and implement database operations in a reactive way on Couchbase using Spring Data Repositories.
We'll cover the basic usages of ReactiveCrudRepository and ReactiveSortingRepository. Additionally, we'll configure our test application with AbstractReactiveCouchbaseConfiguration.
2. Maven Dependencies
Firstly, let's add the necessary dependencies:
<dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-core</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-couchbase-reactive</artifactId> </dependency>
The spring-boot-starter-data-couchbase-reactive dependency contains everything that we need to operate on Couchbase using reactive API.
We'll also include the reactor-core dependency to use Project Reactor API.
3. Configuration
Next, let's define the connection settings between Couchbase and our application.
Let's begin by creating a class that will hold our properties:
@Configuration public class CouchbaseProperties { private List<String> bootstrapHosts; private String bucketName; private String bucketPassword; private int port; public CouchbaseProperties( @Value("${spring.couchbase.bootstrap-hosts}") List<String> bootstrapHosts, @Value("${spring.couchbase.bucket.name}") String bucketName, @Value("${spring.couchbase.bucket.password}") String bucketPassword, @Value("${spring.couchbase.port}") int port) { this.bootstrapHosts = Collections.unmodifiableList(bootstrapHosts); this.bucketName = bucketName; this.bucketPassword = bucketPassword; this.port = port; } // getters }
In order to use reactive support, we should create the configuration class that'll extend AbstractReactiveCouchbaseConfiguration:
@Configuration @EnableReactiveCouchbaseRepositories("com.baeldung.couchbase.domain.repository") public class ReactiveCouchbaseConfiguration extends AbstractReactiveCouchbaseConfiguration { private CouchbaseProperties couchbaseProperties; public ReactiveCouchbaseConfiguration(CouchbaseProperties couchbaseProperties) { this.couchbaseProperties = couchbaseProperties; } @Override protected List<String> getBootstrapHosts() { return couchbaseProperties.getBootstrapHosts(); } @Override protected String getBucketName() { return couchbaseProperties.getBucketName(); } @Override protected String getBucketPassword() { return couchbaseProperties.getBucketPassword(); } @Override public CouchbaseEnvironment couchbaseEnvironment() { return DefaultCouchbaseEnvironment .builder() .bootstrapHttpDirectPort(couchbaseProperties.getPort()) .build(); } }
Additionally, we've used @EnableReactiveCouchbaseRepositories to enable our reactive repositories that'll be under the specified package.
Furthermore, we've overridden couchbaseEnvironment() in order to pass the Couchbase connection port.
4. Repositories
In this section, we'll learn how to create and use the reactive repository. By default, the “all” view is backing most CRUD operations. The custom repository methods are backed by N1QL. If the cluster doesn't support N1QL, the UnsupportedCouchbaseFeatureException will be thrown during initialization.
Firstly, let's create the POJO class that our repositories will work with:
@Document public class Person { @Id private UUID id; private String firstName; //getters and setters }
4.1. View-Based Repository
Now, we'll create a repository for Person:
@Repository @ViewIndexed(designDoc = ViewPersonRepository.DESIGN_DOCUMENT) public interface ViewPersonRepository extends ReactiveCrudRepository<Person, UUID> { String DESIGN_DOCUMENT = "person"; }
The repository extends the ReactiveCrudRepository interface in order to use Reactor API to interact with Couchbase.
Additionally, we can add a custom method and use the @View annotation to make it view-based:
@View(designDocument = ViewPersonRepository.DESIGN_DOCUMENT) Flux<Person> findByFirstName(String firstName);
By default, the query will look for a view named byFirstName. If we want to provide a custom view name, we'll have to use the viewName argument.
Lastly, let's create a simple CRUD test with the help of a test subscriber:
@Test public void shouldSavePerson_findById_thenDeleteIt() { final UUID id = UUID.randomUUID(); final Person person = new Person(id, "John"); personRepository .save(person) .subscribe(); final Mono<Person> byId = personRepository.findById(id); StepVerifier .create(byId) .expectNextMatches(result -> result .getId() .equals(id)) .expectComplete() .verify(); personRepository .delete(person) .subscribe(); }
4.2. N1QL/View-Based Repository
Now, we'll create the reactive repository for Person that'll use the N1QL queries:
@Repository @N1qlPrimaryIndexed public interface N1QLPersonRepository extends ReactiveCrudRepository<Person, UUID> { Flux<Person> findAllByFirstName(String firstName); }
The repository extends the ReactiveCrudRepository in order to use Reactor API as well. In addition, we've added a custom findAllByFirstName method, which creates the N1QL backed query.
After that, let's add the test for the findAllByFirstName method:
@Test public void shouldFindAll_byLastName() { final String firstName = "John"; final Person matchingPerson = new Person(UUID.randomUUID(), firstName); final Person nonMatchingPerson = new Person(UUID.randomUUID(), "NotJohn"); personRepository .save(matchingPerson) .subscribe(); personRepository .save(nonMatchingPerson) .subscribe(); final Flux<Person> allByFirstName = personRepository.findAllByFirstName(firstName); StepVerifier .create(allByFirstName) .expectNext(matchingPerson) .verifyComplete(); }
Furthermore, we'll create a repository that allows us to retrieve people using the sorting abstraction:
@Repository public interface N1QLSortingPersonRepository extends ReactiveSortingRepository<Person, UUID> { Flux<Person> findAllByFirstName(String firstName, Sort sort); }
Lastly, let's write a test to check if the data is actually sorted:
@Test public void shouldFindAll_sortedByFirstName() { final Person firstPerson = new Person(UUID.randomUUID(), "John"); final Person secondPerson = new Person(UUID.randomUUID(), "Mikki"); personRepository .save(firstPerson) .subscribe(); personRepository .save(secondPerson) .subscribe(); final Flux<Person> allByFirstName = personRepository .findAll(Sort.by(Sort.Direction.DESC, "firstName")); StepVerifier .create(allByFirstName) .expectNextMatches(person -> person .getFirstName() .equals(secondPerson.getFirstName())) .expectNextMatches(person -> person .getFirstName() .equals(firstPerson.getFirstName())) .verifyComplete(); }
5. Conclusion
In this article, we've learned how to use repositories using reactive programming with Couchbase and Spring Data Reactive framework.
As always, the code for these examples is available over on Github.