[ BAEL-1869 ]: Couchbase spring reactive article

This commit is contained in:
Lukasz Rys 2019-09-01 20:15:29 +02:00
parent 58bb3a5119
commit f78edd3569
15 changed files with 486 additions and 0 deletions

View File

@ -8,3 +8,4 @@ The "REST With Spring" Classes: http://bit.ly/restwithspring
- [Spring Data Reactive Repositories with MongoDB](http://www.baeldung.com/spring-data-mongodb-reactive)
- [Spring Data MongoDB Tailable Cursors](https://www.baeldung.com/spring-data-mongodb-tailable-cursors)
- [A Quick Look at R2DBC with Spring Data](https://www.baeldung.com/spring-data-r2dbc)
- [Embedded Redis Server with Spring Boot Test]()

View File

@ -14,6 +14,14 @@
</parent>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
@ -105,6 +113,12 @@
<artifactId>httpclient</artifactId>
<version>${httpclient.version}</version>
</dependency>
<dependency>
<groupId>com.couchbase.mock</groupId>
<artifactId>CouchbaseMock</artifactId>
<version>${couchbaseMock.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
@ -224,6 +238,7 @@
<r2dbc-h2.version>0.8.0.M8</r2dbc-h2.version>
<httpclient.version>4.5.2</httpclient.version>
<h2.version>1.4.199</h2.version>
<couchbaseMock.version>1.5.23</couchbaseMock.version>
</properties>
</project>

View File

@ -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);
}
}

View File

@ -0,0 +1,43 @@
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<String> bootstrapHosts;
private final String bucketName;
private final String bucketPassword;
private final int port;
public CouchbaseProperties(
@Value("${spring.couchbase.bootstrap-hosts}") final List<String> 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<String> getBootstrapHosts() {
return bootstrapHosts;
}
public String getBucketName() {
return bucketName;
}
public String getBucketPassword() {
return bucketPassword;
}
public int getPort() {
return port;
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,47 @@
package com.baeldung.couchbase.configuration;
import com.couchbase.client.java.env.CouchbaseEnvironment;
import com.couchbase.client.java.env.DefaultCouchbaseEnvironment;
import org.springframework.context.annotation.Bean;
import org.springframework.data.couchbase.config.AbstractReactiveCouchbaseConfiguration;
import org.springframework.data.couchbase.config.BeanNames;
import org.springframework.data.couchbase.repository.support.IndexManager;
import java.util.List;
public abstract class ReactiveCouchbaseConfiguration extends AbstractReactiveCouchbaseConfiguration {
private CouchbaseProperties couchbaseProperties;
public ReactiveCouchbaseConfiguration(final CouchbaseProperties couchbaseProperties) {
this.couchbaseProperties = couchbaseProperties;
}
@Bean(name = BeanNames.COUCHBASE_INDEX_MANAGER)
public IndexManager indexManager() {
return new IndexManager(true, true, false);
}
@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();
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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<Person, UUID> {
Flux<Person> findAllByFirstName(final String firstName);
}

View File

@ -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<Person, UUID> {
}

View File

@ -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<Person, UUID> {
String DESIGN_DOCUMENT = "persons";
@View(designDocument = ViewPersonRepository.DESIGN_DOCUMENT)
Flux<Person> findByFirstName(String firstName);
}

View File

@ -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.List;
@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(), List.of(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();
}
}

View File

@ -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<Person> 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();
}
}
}
}

View File

@ -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<Person> 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();
}
}
}
}

View File

@ -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<Person> 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<Person> 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();
}
}
}
}