Перейти к основному содержимому

Реактивные репозитории Spring Data с Couchbase

· 4 мин. чтения

Задача: Сумма двух чисел

Напишите функцию twoSum. Которая получает массив целых чисел nums и целую сумму target, а возвращает индексы двух чисел, сумма которых равна target. Любой набор входных данных имеет ровно одно решение, и вы не можете использовать один и тот же элемент дважды. Ответ можно возвращать в любом порядке...

ANDROMEDA

1. Обзор

В этом руководстве мы узнаем, как настраивать и реализовывать операции с базой данных реактивным способом на Couchbase с использованием репозиториев данных Spring.

Мы рассмотрим основные способы использования ReactiveCrudRepository и ReactiveSortingRepository . Кроме того, мы настроим наше тестовое приложение с помощью AbstractReactiveCouchbaseConfiguration .

2. Зависимости Maven

Во-первых, добавим необходимые зависимости:

<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>

Зависимость spring-boot-starter-data-couchbase-reactive содержит все, что нам нужно для работы с Couchbase с использованием реактивного API.

Мы также добавим зависимость реактора от ядра для использования Project Reactor API.

3. Конфигурация

Далее давайте определим настройки соединения между Couchbase и нашим приложением.

Давайте начнем с создания класса, который будет содержать наши свойства:

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

Чтобы использовать реактивную поддержку, мы должны создать класс конфигурации, который будет расширять AbstractReactiveCouchbaseConfiguration :

@Configuration
@EnableReactiveCouchbaseRepositories("com.foreach.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();
}
}

Кроме того, мы использовали @EnableReactiveCouchbaseRepositories , чтобы включить наши реактивные репозитории, которые будут находиться в указанном пакете.

Кроме того, мы переопределили CouchbaseEnvironment() , чтобы передать порт подключения Couchbase.

4. Репозитории

В этом разделе мы узнаем, как создать и использовать реактивный репозиторий. По умолчанию представление «все» поддерживает большинство операций CRUD. Пользовательские методы репозитория поддерживаются N1QL . Если кластер не поддерживает N1QL, во время инициализации будет выдано исключение UnsupportedCouchbaseFeatureException .

Во-первых, давайте создадим класс POJO, с которым будут работать наши репозитории:

@Document
public class Person {
@Id private UUID id;
private String firstName;

//getters and setters
}

4.1. Репозиторий на основе представлений

Теперь мы создадим репозиторий для Person :

@Repository
@ViewIndexed(designDoc = ViewPersonRepository.DESIGN_DOCUMENT)
public interface ViewPersonRepository extends ReactiveCrudRepository<Person, UUID> {

String DESIGN_DOCUMENT = "person";
}

Репозиторий расширяет интерфейс ReactiveCrudRepository , чтобы использовать Reactor API для взаимодействия с Couchbase.

Кроме того, мы можем добавить пользовательский метод и использовать аннотацию @View , чтобы сделать его основанным на представлении:

@View(designDocument = ViewPersonRepository.DESIGN_DOCUMENT)
Flux<Person> findByFirstName(String firstName);

По умолчанию запрос будет искать представление с именем byFirstName . Если мы хотим предоставить пользовательское имя представления, нам придется использовать аргумент viewName .

Наконец, давайте создадим простой CRUD-тест с помощью тестового подписчика:

@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/репозиторий на основе представлений

Теперь мы создадим реактивный репозиторий для Person , который будет использовать запросы N1QL:

@Repository
@N1qlPrimaryIndexed
public interface N1QLPersonRepository extends ReactiveCrudRepository<Person, UUID> {
Flux<Person> findAllByFirstName(String firstName);
}

Репозиторий расширяет ReactiveCrudRepository , чтобы также использовать Reactor API. Кроме того, мы добавили собственный метод findAllByFirstName , который создает запрос, поддерживаемый N1QL.

После этого добавим тест для метода findAllByFirstName :

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

Кроме того, мы создадим репозиторий, который позволит нам извлекать людей с помощью абстракции сортировки:

@Repository
public interface N1QLSortingPersonRepository extends ReactiveSortingRepository<Person, UUID> {
Flux<Person> findAllByFirstName(String firstName, Sort sort);
}

Наконец, давайте напишем тест, чтобы проверить, действительно ли данные отсортированы:

@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. Вывод

В этой статье мы узнали, как использовать репозитории с помощью реактивного программирования с Couchbase и Spring Data Reactive framework.

Как всегда, код этих примеров доступен на Github .