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

Краткий обзор R2DBC с Spring Data

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

1. Введение

R2DBC (Reactive Relational Database Connectivity) — это проект, представленный Pivotal во время Spring One Platform 2018. Он предназначен для создания реактивного API для баз данных SQL.

Другими словами, эта попытка создает соединение с базой данных с использованием полностью неблокирующих драйверов.

В этом руководстве мы рассмотрим пример приложения, использующего Spring Data R2BDC. Руководство по более низкоуровневому API R2DBC можно найти в нашей предыдущей статье .

2. Наш первый проект Spring Data R2DBC

Начнем с того, что проект R2DBC появился совсем недавно. На данный момент только PostGres, MSSQL и H2 имеют драйверы R2DBC. Кроме того, мы не можем использовать с ним все функции Spring Boot. Поэтому есть некоторые шаги, которые нам нужно будет добавить вручную. Но мы можем использовать такие проекты, как Spring Data , чтобы помочь нам.

Сначала мы создадим проект Maven. На данный момент у R2DBC есть несколько проблем с зависимостями, поэтому наш pom.xml будет больше, чем обычно.

В рамках этой статьи мы будем использовать H2 в качестве нашей базы данных и создадим реактивные функции CRUD для нашего приложения.

Давайте откроем pom.xml сгенерированного проекта и добавим соответствующие зависимости, а также некоторые репозитории Spring раннего выпуска:

<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-r2dbc</artifactId>
<version>1.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>io.r2dbc</groupId>
<artifactId>r2dbc-h2</artifactId>
<version>0.8.1.RELEASE</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.199</version>
</dependency>
</dependencies>

Другие необходимые артефакты включают Lombok, Spring WebFlux и несколько других, которые дополняют зависимости нашего проекта .

3. Фабрика соединений

При работе с базой данных нам нужна фабрика соединений. Так что, конечно же, нам понадобится то же самое с R2DBC.

Итак, теперь мы добавим детали для подключения к нашему экземпляру:

@Configuration
@EnableR2dbcRepositories
class R2DBCConfiguration extends AbstractR2dbcConfiguration {
@Bean
public H2ConnectionFactory connectionFactory() {
return new H2ConnectionFactory(
H2ConnectionConfiguration.builder()
.url("mem:testdb;DB_CLOSE_DELAY=-1;")
.username("sa")
.build()
);
}
}

Первое, что мы замечаем в приведенном выше коде, это @EnableR2dbcRepositories . Нам нужна эта аннотация, чтобы использовать функциональность Spring Data. Кроме того, мы расширяем AbstractR2dbcConfiguration , так как он предоставит много bean-компонентов, которые нам понадобятся позже.

4. Наше первое приложение R2DBC

Наш следующий шаг — создать репозиторий:

interface PlayerRepository extends ReactiveCrudRepository<Player, Integer> {}

Интерфейс ReactiveCrudRepository очень полезен. Он обеспечивает, например, базовую функциональность CRUD.

Наконец, мы определим класс нашей модели. Мы будем использовать Lombok, чтобы избежать шаблонного кода:

@Data
@NoArgsConstructor
@AllArgsConstructor
class Player {
@Id
Integer id;
String name;
Integer age;
}

5. Тестирование

Пришло время протестировать наш код. Итак, начнем с создания нескольких тестовых случаев:

@Test
public void whenDeleteAll_then0IsExpected() {
playerRepository.deleteAll()
.as(StepVerifier::create)
.expectNextCount(0)
.verifyComplete();
}

@Test
public void whenInsert6_then6AreExpected() {
insertPlayers();
playerRepository.findAll()
.as(StepVerifier::create)
.expectNextCount(6)
.verifyComplete();
}

6. Пользовательские запросы

Мы также можем генерировать пользовательские запросы . Чтобы добавить его, нам нужно изменить наш PlayerRepository :

@Query("select id, name, age from player where name = $1")
Flux<Player> findAllByName(String name);

@Query("select * from player where age = $1")
Flux<Player> findByAge(int age);

В дополнение к существующим тестам мы добавим тесты в наш недавно обновленный репозиторий:

@Test
public void whenSearchForCR7_then1IsExpected() {
insertPlayers();
playerRepository.findAllByName("CR7")
.as(StepVerifier::create)
.expectNextCount(1)
.verifyComplete();
}

@Test
public void whenSearchFor32YearsOld_then2AreExpected() {
insertPlayers();
playerRepository.findByAge(32)
.as(StepVerifier::create)
.expectNextCount(2)
.verifyComplete();
}

private void insertPlayers() {
List<Player> players = Arrays.asList(
new Player(1, "Kaka", 37),
new Player(2, "Messi", 32),
new Player(3, "Mbappé", 20),
new Player(4, "CR7", 34),
new Player(5, "Lewandowski", 30),
new Player(6, "Cavani", 32)
);
playerRepository.saveAll(players).subscribe();
}

7. Пакеты

Еще одна особенность R2DBC — создание пакетов. Пакет полезен при выполнении нескольких операторов SQL, поскольку они будут выполняться лучше, чем отдельные операции.

Для создания пакета нам нужен объект Connection :

Batch batch = connection.createBatch();

После того, как наше приложение создаст экземпляр пакетной службы, мы можем добавить столько операторов SQL, сколько захотим. Чтобы выполнить его, мы вызовем метод execute() . Результатом пакета является издатель , который возвращает объект результата для каждого оператора.

Итак, давайте перейдем к коду и посмотрим, как мы можем создать пакет :

@Test
public void whenBatchHas2Operations_then2AreExpected() {
Mono.from(factory.create())
.flatMapMany(connection -> Flux.from(connection
.createBatch()
.add("select * from player")
.add("select * from player")
.execute()))
.as(StepVerifier::create)
.expectNextCount(2)
.verifyComplete();
}

8. Заключение

Подводя итог, R2DBC все еще находится на ранней стадии. Это попытка создать SPI, который будет определять реактивный API для баз данных SQL. При использовании с Spring WebFlux R2DBC позволяет нам написать приложение, которое асинхронно обрабатывает данные сверху и до самой базы данных.

Как всегда, код доступен на GitHub .