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 .