diff --git a/spring-5-data-reactive/pom.xml b/spring-5-data-reactive/pom.xml
index 5372803842..296da87529 100644
--- a/spring-5-data-reactive/pom.xml
+++ b/spring-5-data-reactive/pom.xml
@@ -14,6 +14,14 @@
+
+ io.projectreactor
+ reactor-core
+
+
+ org.springframework.boot
+ spring-boot-starter-data-couchbase-reactive
+
org.springframework.boot
spring-boot-starter-data-mongodb-reactive
@@ -105,6 +113,12 @@
httpclient
${httpclient.version}
+
+ com.couchbase.mock
+ CouchbaseMock
+ ${couchbaseMock.version}
+ test
+
@@ -224,6 +238,7 @@
0.8.0.M8
4.5.2
1.4.199
+ 1.5.23
diff --git a/spring-5-data-reactive/src/main/java/com/baeldung/couchbase/ReactiveCouchbaseApplication.java b/spring-5-data-reactive/src/main/java/com/baeldung/couchbase/ReactiveCouchbaseApplication.java
new file mode 100644
index 0000000000..917bcfdaa8
--- /dev/null
+++ b/spring-5-data-reactive/src/main/java/com/baeldung/couchbase/ReactiveCouchbaseApplication.java
@@ -0,0 +1,12 @@
+package com.baeldung.couchbase;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class ReactiveCouchbaseApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(ReactiveCouchbaseApplication.class, args);
+ }
+}
diff --git a/spring-5-data-reactive/src/main/java/com/baeldung/couchbase/configuration/CouchbaseProperties.java b/spring-5-data-reactive/src/main/java/com/baeldung/couchbase/configuration/CouchbaseProperties.java
new file mode 100644
index 0000000000..480c96e986
--- /dev/null
+++ b/spring-5-data-reactive/src/main/java/com/baeldung/couchbase/configuration/CouchbaseProperties.java
@@ -0,0 +1,40 @@
+package com.baeldung.couchbase.configuration;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.Collections;
+import java.util.List;
+
+@Configuration
+public class CouchbaseProperties {
+
+ private final List bootstrapHosts;
+ private final String bucketName;
+ private final String bucketPassword;
+ private final int port;
+
+ public CouchbaseProperties(@Value("${spring.couchbase.bootstrap-hosts}") final List bootstrapHosts, @Value("${spring.couchbase.bucket.name}") final String bucketName, @Value("${spring.couchbase.bucket.password}") final String bucketPassword,
+ @Value("${spring.couchbase.port}") final int port) {
+ this.bootstrapHosts = Collections.unmodifiableList(bootstrapHosts);
+ this.bucketName = bucketName;
+ this.bucketPassword = bucketPassword;
+ this.port = port;
+ }
+
+ public List getBootstrapHosts() {
+ return bootstrapHosts;
+ }
+
+ public String getBucketName() {
+ return bucketName;
+ }
+
+ public String getBucketPassword() {
+ return bucketPassword;
+ }
+
+ public int getPort() {
+ return port;
+ }
+}
diff --git a/spring-5-data-reactive/src/main/java/com/baeldung/couchbase/configuration/N1QLReactiveCouchbaseConfiguration.java b/spring-5-data-reactive/src/main/java/com/baeldung/couchbase/configuration/N1QLReactiveCouchbaseConfiguration.java
new file mode 100644
index 0000000000..059bd36cae
--- /dev/null
+++ b/spring-5-data-reactive/src/main/java/com/baeldung/couchbase/configuration/N1QLReactiveCouchbaseConfiguration.java
@@ -0,0 +1,15 @@
+package com.baeldung.couchbase.configuration;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.data.couchbase.repository.config.EnableReactiveCouchbaseRepositories;
+
+@Configuration
+@EnableReactiveCouchbaseRepositories("com.baeldung.couchbase.domain.repository.n1ql")
+@Primary
+public class N1QLReactiveCouchbaseConfiguration extends ReactiveCouchbaseConfiguration {
+
+ public N1QLReactiveCouchbaseConfiguration(CouchbaseProperties couchbaseProperties) {
+ super(couchbaseProperties);
+ }
+}
diff --git a/spring-5-data-reactive/src/main/java/com/baeldung/couchbase/configuration/ReactiveCouchbaseConfiguration.java b/spring-5-data-reactive/src/main/java/com/baeldung/couchbase/configuration/ReactiveCouchbaseConfiguration.java
new file mode 100644
index 0000000000..2f3b5486d8
--- /dev/null
+++ b/spring-5-data-reactive/src/main/java/com/baeldung/couchbase/configuration/ReactiveCouchbaseConfiguration.java
@@ -0,0 +1,39 @@
+package com.baeldung.couchbase.configuration;
+
+import com.couchbase.client.java.env.CouchbaseEnvironment;
+import com.couchbase.client.java.env.DefaultCouchbaseEnvironment;
+import org.springframework.data.couchbase.config.AbstractReactiveCouchbaseConfiguration;
+
+import java.util.List;
+
+public abstract class ReactiveCouchbaseConfiguration extends AbstractReactiveCouchbaseConfiguration {
+
+ private CouchbaseProperties couchbaseProperties;
+
+ public ReactiveCouchbaseConfiguration(final CouchbaseProperties couchbaseProperties) {
+ this.couchbaseProperties = couchbaseProperties;
+ }
+
+ @Override
+ protected List 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();
+ }
+}
diff --git a/spring-5-data-reactive/src/main/java/com/baeldung/couchbase/configuration/ViewReactiveCouchbaseConfiguration.java b/spring-5-data-reactive/src/main/java/com/baeldung/couchbase/configuration/ViewReactiveCouchbaseConfiguration.java
new file mode 100644
index 0000000000..9b4d9b0319
--- /dev/null
+++ b/spring-5-data-reactive/src/main/java/com/baeldung/couchbase/configuration/ViewReactiveCouchbaseConfiguration.java
@@ -0,0 +1,13 @@
+package com.baeldung.couchbase.configuration;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.couchbase.repository.config.EnableReactiveCouchbaseRepositories;
+
+@Configuration
+@EnableReactiveCouchbaseRepositories("com.baeldung.couchbase.domain.repository.view")
+public class ViewReactiveCouchbaseConfiguration extends ReactiveCouchbaseConfiguration {
+
+ public ViewReactiveCouchbaseConfiguration(CouchbaseProperties couchbaseProperties) {
+ super(couchbaseProperties);
+ }
+}
diff --git a/spring-5-data-reactive/src/main/java/com/baeldung/couchbase/domain/Person.java b/spring-5-data-reactive/src/main/java/com/baeldung/couchbase/domain/Person.java
new file mode 100644
index 0000000000..285de34df8
--- /dev/null
+++ b/spring-5-data-reactive/src/main/java/com/baeldung/couchbase/domain/Person.java
@@ -0,0 +1,43 @@
+package com.baeldung.couchbase.domain;
+
+import org.springframework.data.annotation.Id;
+import org.springframework.data.couchbase.core.mapping.Document;
+
+import java.util.Objects;
+import java.util.UUID;
+
+@Document
+public class Person {
+
+ @Id private UUID id;
+ private String firstName;
+
+ public Person(final UUID id, final String firstName) {
+ this.id = id;
+ this.firstName = firstName;
+ }
+
+ private Person() {
+ }
+
+ public UUID getId() {
+ return id;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Person person = (Person) o;
+ return Objects.equals(id, person.id) && Objects.equals(firstName, person.firstName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, firstName);
+ }
+}
diff --git a/spring-5-data-reactive/src/main/java/com/baeldung/couchbase/domain/repository/n1ql/N1QLPersonRepository.java b/spring-5-data-reactive/src/main/java/com/baeldung/couchbase/domain/repository/n1ql/N1QLPersonRepository.java
new file mode 100644
index 0000000000..6f73a77ceb
--- /dev/null
+++ b/spring-5-data-reactive/src/main/java/com/baeldung/couchbase/domain/repository/n1ql/N1QLPersonRepository.java
@@ -0,0 +1,16 @@
+package com.baeldung.couchbase.domain.repository.n1ql;
+
+import com.baeldung.couchbase.domain.Person;
+import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed;
+import org.springframework.data.repository.reactive.ReactiveCrudRepository;
+import org.springframework.stereotype.Repository;
+import reactor.core.publisher.Flux;
+
+import java.util.UUID;
+
+@Repository
+@N1qlPrimaryIndexed
+public interface N1QLPersonRepository extends ReactiveCrudRepository {
+
+ Flux findAllByFirstName(final String firstName);
+}
diff --git a/spring-5-data-reactive/src/main/java/com/baeldung/couchbase/domain/repository/n1ql/N1QLSortingPersonRepository.java b/spring-5-data-reactive/src/main/java/com/baeldung/couchbase/domain/repository/n1ql/N1QLSortingPersonRepository.java
new file mode 100644
index 0000000000..57dd149425
--- /dev/null
+++ b/spring-5-data-reactive/src/main/java/com/baeldung/couchbase/domain/repository/n1ql/N1QLSortingPersonRepository.java
@@ -0,0 +1,13 @@
+package com.baeldung.couchbase.domain.repository.n1ql;
+
+import com.baeldung.couchbase.domain.Person;
+import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed;
+import org.springframework.data.repository.reactive.ReactiveSortingRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.UUID;
+
+@Repository
+@N1qlPrimaryIndexed
+public interface N1QLSortingPersonRepository extends ReactiveSortingRepository {
+}
diff --git a/spring-5-data-reactive/src/main/java/com/baeldung/couchbase/domain/repository/view/ViewPersonRepository.java b/spring-5-data-reactive/src/main/java/com/baeldung/couchbase/domain/repository/view/ViewPersonRepository.java
new file mode 100644
index 0000000000..28a7ec1b78
--- /dev/null
+++ b/spring-5-data-reactive/src/main/java/com/baeldung/couchbase/domain/repository/view/ViewPersonRepository.java
@@ -0,0 +1,20 @@
+package com.baeldung.couchbase.domain.repository.view;
+
+import com.baeldung.couchbase.domain.Person;
+import org.springframework.data.couchbase.core.query.View;
+import org.springframework.data.couchbase.core.query.ViewIndexed;
+import org.springframework.data.repository.reactive.ReactiveCrudRepository;
+import org.springframework.stereotype.Repository;
+import reactor.core.publisher.Flux;
+
+import java.util.UUID;
+
+@Repository
+@ViewIndexed(designDoc = ViewPersonRepository.DESIGN_DOCUMENT)
+public interface ViewPersonRepository extends ReactiveCrudRepository {
+
+ String DESIGN_DOCUMENT = "persons";
+
+ @View(designDocument = ViewPersonRepository.DESIGN_DOCUMENT)
+ Flux findByFirstName(String firstName);
+}
diff --git a/spring-5-data-reactive/src/test/java/com/baeldung/couchbase/domain/repository/CouchbaseMockConfiguration.java b/spring-5-data-reactive/src/test/java/com/baeldung/couchbase/domain/repository/CouchbaseMockConfiguration.java
new file mode 100644
index 0000000000..2a09fce4b0
--- /dev/null
+++ b/spring-5-data-reactive/src/test/java/com/baeldung/couchbase/domain/repository/CouchbaseMockConfiguration.java
@@ -0,0 +1,54 @@
+package com.baeldung.couchbase.domain.repository;
+
+import com.baeldung.couchbase.configuration.CouchbaseProperties;
+import com.couchbase.mock.Bucket;
+import com.couchbase.mock.BucketConfiguration;
+import com.couchbase.mock.CouchbaseMock;
+import org.springframework.boot.test.context.TestConfiguration;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.Collections;
+
+@TestConfiguration
+public class CouchbaseMockConfiguration {
+
+ private CouchbaseMock couchbaseMock;
+
+ public CouchbaseMockConfiguration(final CouchbaseProperties couchbaseProperties) {
+ final BucketConfiguration bucketConfiguration = new BucketConfiguration();
+ bucketConfiguration.numNodes = 1;
+ bucketConfiguration.numReplicas = 1;
+ bucketConfiguration.numVBuckets = 1024;
+ bucketConfiguration.name = couchbaseProperties.getBucketName();
+ bucketConfiguration.type = Bucket.BucketType.COUCHBASE;
+ bucketConfiguration.password = couchbaseProperties.getBucketPassword();
+
+ try {
+ couchbaseMock = new CouchbaseMock(couchbaseProperties.getPort(), Collections.singletonList(bucketConfiguration));
+ } catch (final IOException ex) {
+ throw new UncheckedIOException(ex);
+ }
+ }
+
+ @PostConstruct
+ public void postConstruct() {
+ try {
+ couchbaseMock.start();
+ } catch (final IOException ex) {
+ throw new UncheckedIOException(ex);
+ }
+ try {
+ couchbaseMock.waitForStartup();
+ } catch (final InterruptedException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ @PreDestroy
+ public void preDestroy() {
+ couchbaseMock.stop();
+ }
+}
diff --git a/spring-5-data-reactive/src/test/java/com/baeldung/couchbase/domain/repository/n1ql/N1QLPersonRepositoryLiveTest.java b/spring-5-data-reactive/src/test/java/com/baeldung/couchbase/domain/repository/n1ql/N1QLPersonRepositoryLiveTest.java
new file mode 100644
index 0000000000..0c1b6bde6c
--- /dev/null
+++ b/spring-5-data-reactive/src/test/java/com/baeldung/couchbase/domain/repository/n1ql/N1QLPersonRepositoryLiveTest.java
@@ -0,0 +1,55 @@
+package com.baeldung.couchbase.domain.repository.n1ql;
+
+import com.baeldung.couchbase.domain.Person;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+import reactor.core.publisher.Flux;
+import reactor.test.StepVerifier;
+
+import java.util.UUID;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class N1QLPersonRepositoryLiveTest {
+
+ @Autowired private N1QLPersonRepository personRepository;
+
+ @Test
+ public void shouldFindAll_byLastName() {
+ //Given
+ final String firstName = "John";
+ final Person matchingPerson = new Person(UUID.randomUUID(), firstName);
+ final Person nonMatchingPerson = new Person(UUID.randomUUID(), "NotJohn");
+ wrap(() -> {
+ personRepository
+ .save(matchingPerson)
+ .subscribe();
+ personRepository
+ .save(nonMatchingPerson)
+ .subscribe();
+ //When
+ final Flux allByFirstName = personRepository.findAllByFirstName(firstName);
+ //Then
+ StepVerifier
+ .create(allByFirstName)
+ .expectNext(matchingPerson)
+ .verifyComplete();
+
+ }, matchingPerson, nonMatchingPerson);
+ }
+
+ private void wrap(final Runnable runnable, final Person... people) {
+ try {
+ runnable.run();
+ } finally {
+ for (final Person person : people) {
+ personRepository
+ .delete(person)
+ .subscribe();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-5-data-reactive/src/test/java/com/baeldung/couchbase/domain/repository/n1ql/N1QLSortingPersonRepositoryLiveTest.java b/spring-5-data-reactive/src/test/java/com/baeldung/couchbase/domain/repository/n1ql/N1QLSortingPersonRepositoryLiveTest.java
new file mode 100644
index 0000000000..4ba2206e0a
--- /dev/null
+++ b/spring-5-data-reactive/src/test/java/com/baeldung/couchbase/domain/repository/n1ql/N1QLSortingPersonRepositoryLiveTest.java
@@ -0,0 +1,60 @@
+package com.baeldung.couchbase.domain.repository.n1ql;
+
+import com.baeldung.couchbase.domain.Person;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.data.domain.Sort;
+import org.springframework.test.context.junit4.SpringRunner;
+import reactor.core.publisher.Flux;
+import reactor.test.StepVerifier;
+
+import java.util.UUID;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class N1QLSortingPersonRepositoryLiveTest {
+
+ @Autowired private N1QLSortingPersonRepository personRepository;
+
+ @Test
+ public void shouldFindAll_sortedByFirstName() {
+ //Given
+ final Person firstPerson = new Person(UUID.randomUUID(), "John");
+ final Person secondPerson = new Person(UUID.randomUUID(), "Mikki");
+ wrap(() -> {
+ personRepository
+ .save(firstPerson)
+ .subscribe();
+ personRepository
+ .save(secondPerson)
+ .subscribe();
+ //When
+ final Flux allByFirstName = personRepository.findAll(Sort.by(Sort.Direction.DESC, "firstName"));
+ //Then
+ StepVerifier
+ .create(allByFirstName)
+ .expectNextMatches(person -> person
+ .getFirstName()
+ .equals(secondPerson.getFirstName()))
+ .expectNextMatches(person -> person
+ .getFirstName()
+ .equals(firstPerson.getFirstName()))
+ .verifyComplete();
+ }, firstPerson, secondPerson);
+ }
+
+ //workaround for deleteAll()
+ private void wrap(final Runnable runnable, final Person... people) {
+ try {
+ runnable.run();
+ } finally {
+ for (final Person person : people) {
+ personRepository
+ .delete(person)
+ .subscribe();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-5-data-reactive/src/test/java/com/baeldung/couchbase/domain/repository/view/ViewPersonRepositoryIntegrationTest.java b/spring-5-data-reactive/src/test/java/com/baeldung/couchbase/domain/repository/view/ViewPersonRepositoryIntegrationTest.java
new file mode 100644
index 0000000000..712e7b0d37
--- /dev/null
+++ b/spring-5-data-reactive/src/test/java/com/baeldung/couchbase/domain/repository/view/ViewPersonRepositoryIntegrationTest.java
@@ -0,0 +1,79 @@
+package com.baeldung.couchbase.domain.repository.view;
+
+import com.baeldung.couchbase.configuration.CouchbaseProperties;
+import com.baeldung.couchbase.configuration.ViewReactiveCouchbaseConfiguration;
+import com.baeldung.couchbase.domain.Person;
+import com.baeldung.couchbase.domain.repository.CouchbaseMockConfiguration;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+
+import java.util.UUID;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(properties = { "spring.couchbase.port=10010" }, classes = { ViewReactiveCouchbaseConfiguration.class, CouchbaseProperties.class, CouchbaseMockConfiguration.class })
+public class ViewPersonRepositoryIntegrationTest {
+
+ @Autowired private ViewPersonRepository personRepository;
+
+ @Test
+ public void shouldSavePerson_findById_thenDeleteIt() {
+ //Given
+ final UUID id = UUID.randomUUID();
+ final Person person = new Person(id, "John");
+ wrap(() -> {
+ personRepository
+ .save(person)
+ .subscribe();
+ //When
+ final Mono byId = personRepository.findById(id);
+ //Then
+ StepVerifier
+ .create(byId)
+ .expectNextMatches(result -> result
+ .getId()
+ .equals(id))
+ .expectComplete()
+ .verify();
+ }, person);
+ }
+
+ @Test
+ public void shouldFindAll_thenDeleteIt() {
+ //Given
+ final Person person = new Person(UUID.randomUUID(), "John");
+ final Person secondPerson = new Person(UUID.randomUUID(), "Mikki");
+ wrap(() -> {
+ personRepository
+ .save(person)
+ .subscribe();
+ personRepository
+ .save(secondPerson)
+ .subscribe();
+ //When
+ final Flux all = personRepository.findAll();
+ //Then
+ StepVerifier
+ .create(all)
+ .expectNextCount(2)
+ .verifyComplete();
+ }, person, secondPerson);
+ }
+
+ private void wrap(final Runnable runnable, final Person... people) {
+ try {
+ runnable.run();
+ } finally {
+ for (final Person person : people) {
+ personRepository
+ .delete(person)
+ .subscribe();
+ }
+ }
+ }
+}
\ No newline at end of file