Не так давно был выпущен реактивный вариант драйвера JDBC. Известный как R2DBC. Это позволяет передавать данные асинхронно на любые конечные точки, которые подписались на него. Использование реактивного драйвера, такого как R2DBC, вместе с Spring Web Flux позволяет вам написать полноценное приложение, которое обрабатывает прием и отправку данных асинхронно. В этом посте мы сосредоточимся на базе данных. От подключения к базе данных, а затем, наконец, сохранения и извлечения данных. Для этого мы будем использовать данные Spring. Как и во всех модулях Spring Data, он предоставляет нам готовую конфигурацию. Уменьшение объема шаблонного кода, который нам нужно написать для настройки нашего приложения. Кроме того, он обеспечивает слой драйвера базы данных, который облегчает выполнение простых задач, а более сложные задачи немного менее болезненны.
Для содержания этого поста я использую базу данных Postgres. На момент написания статьи только Postgres, H2 и Microsoft SQL Server имеют собственные реализации драйверов R2 ODBC.
Ранее я написал два поста о библиотеках данных reactive Spring, один на Монго и еще о Кассандре . Возможно, вы заметили, что ни одна из этих баз данных не является базой данных СУБД. Теперь уже давно доступны другие реактивные драйверы (я написал пост в Монго почти 2 года назад). но на момент написания реактивный драйвер для базы данных СУБД все еще является довольно новой вещью. Этот пост будет выполнен в аналогичном формате.
Кроме того, я также написал пост об использовании Spring Web Flux, о котором я упоминал во введении. Не стесняйтесь взглянуть на это, если вы заинтересованы в создании полностью реактивного веб-приложения.
Зависимости
org.springframework.boot spring-boot-starter org.springframework.data spring-data-r2dbc 1.0.0.M1 io.r2dbc r2dbc-postgresql 1.0.0.M6 io.projectreactor reactor-core repository.spring.milestone Spring Milestone Repository http://repo.spring.io/milestone
Здесь следует отметить несколько моментов.
Чем больше вы используете Spring Boot, тем больше вы привыкнете импортировать одну зависимость spring-boot-starter для классной вещи, которую вы хотите сделать. Например, я надеялся, что будет зависимость spring-boot-starter-r2dbc , но, к сожалению, ее нет. Еще. Проще говоря, эта библиотека находится на более новой стороне и на момент написания не имеет собственного модуля загрузки Spring, который содержит любые необходимые зависимости, а также более быструю настройку с помощью автоматической настройки. Я уверен, что в какой-то момент эти вещи появятся и сделают настройку драйвера R2DBC еще проще.
На данный момент нам нужно будет заполнить несколько дополнительных зависимостей вручную.
Кроме того, в библиотеках R2 DB C есть только выпуски Milestone (больше доказательств того, что они новые), поэтому нам нужно убедиться, что мы включили репозиторий Spring Milestone. Вероятно, мне придется обновить этот пост в будущем, когда он получит релизную версию.
Подключение к базе данных
Благодаря Spring Data, выполняющей большую часть работы за нас, единственный компонент, который необходимо создать вручную, – это ConnectionFactory , содержащий сведения о подключении к базе данных:
@Configuration
@EnableR2dbcRepositories
class DatabaseConfiguration(
@Value("\${spring.data.postgres.host}") private val host: String,
@Value("\${spring.data.postgres.port}") private val port: Int,
@Value("\${spring.data.postgres.database}") private val database: String,
@Value("\${spring.data.postgres.username}") private val username: String,
@Value("\${spring.data.postgres.password}") private val password: String
) : AbstractR2dbcConfiguration() {
override fun connectionFactory(): ConnectionFactory {
return PostgresqlConnectionFactory(
PostgresqlConnectionConfiguration.builder()
.host(host)
.port(port)
.database(database)
.username(username)
.password(password).build()
)
}
}
Первое, на что здесь следует обратить внимание, – это расширение конфигурации odbc Abstract R2 . Этот класс содержит множество компонентов, которые нам больше не нужно создавать вручную. Реализация ConnectionFactory является единственным требованием класса, поскольку оно требуется для создания клиента базы данных Боб. Такого рода структура типична для модулей данных Spring, поэтому она кажется довольно знакомой при опробовании другой. Кроме того, я ожидаю, что эта ручная настройка будет удалена, как только станет доступна автоматическая настройка, и будет управляться исключительно с помощью application.properties .
Я включил здесь свойство порт , но если вы не играли с вашей конфигурацией Postgres, вы можете полагаться на значение по умолчанию 5432 .
Четыре свойства: хост , база данных , имя пользователя и пароль , определенный фабрикой соединений Postgresql , являются минимальным минимумом для его работы. Не меньше и вы столкнетесь с исключениями во время запуска.
Используя эту конфигурацию, Spring может подключаться к запущенному экземпляру Postgres.
Последней заслуживающей внимания информацией из этого примера является использование @EnableR2dbcRepositories . Эта аннотация инструктирует Spring находить любые интерфейсы репозитория, расширяющие репозиторий Spring интерфейс. Это используется в качестве базового интерфейса для инструментирования хранилищ данных Spring. Мы рассмотрим это немного подробнее в следующем разделе. Основная информация, которую следует извлечь отсюда, заключается в том, что вам необходимо использовать аннотацию @EnableR2dbcRepositories , чтобы в полной мере использовать возможности Spring Data.
Создание хранилища данных Spring
Как упоминалось выше, в этом разделе мы рассмотрим добавление хранилища данных Spring. Эти репозитории являются приятной особенностью Spring Data, а это означает, что вам не нужно выписывать кучу дополнительного кода, чтобы просто написать запрос. К сожалению, по крайней мере, на данный момент Spring R2DBC не может выводить запросы так же, как в настоящее время делают другие модули данных Spring (я уверен, что это будет добавлено в какой-то момент). Это означает, что вам нужно будет использовать аннотацию @Query и написать SQL вручную. Давайте взглянем:
@Repository interface PersonRepository : R2dbcRepository{ @Query("SELECT * FROM people WHERE name = $1") fun findAllByName(name: String): Flux @Query("SELECT * FROM people WHERE age = $1") fun findAllByAge(age: Int): Flux }
Этот интерфейс расширяет R2dbcрепозиция . Это, в свою очередь, расширяет Реактивный CrudRepository а затем вниз в Репозиторий . Реактивный CrudRepository предоставляет стандартные функции CRUD и, насколько я понимаю, R2dbcRepository не предоставляет никаких дополнительных функций и вместо этого представляет собой интерфейс, созданный для лучшего ситуационного именования.
R2dbcRepository принимает два общих параметра, один из которых является классом сущностей, который он принимает в качестве входных данных и выдает в качестве выходных. Второй – это тип первичного ключа. Поэтому в этой ситуации классом Person управляет PersonRepository (имеет смысл) и полем первичного ключа внутри Лицо является Int .
Типы возвращаемых функций в этом классе и функции, предоставляемые Реактивным CrudRepository , являются Flux и Mono (здесь не видно). Это типы реакторов проекта, которые Spring использует в качестве типов реактивных потоков по умолчанию. Поток представляет собой поток из нескольких элементов, тогда как Mono – это единственный результат.
Наконец, как я упоминал перед примером, каждая функция помечена @Query . Синтаксис довольно прямолинеен, а SQL представляет собой строку внутри аннотации. В $1 ( $2 , $3 , и т.д… для большего количества входных данных) представляет значение, вводимое в функцию. Как только вы это сделаете, Spring обработает все остальное и передаст входные данные в соответствующий входной параметр, соберет результаты и сопоставит их с назначенным классом сущностей репозитория.
Очень быстрый взгляд на сущность
Не буду здесь много говорить, а просто покажу класс Person , используемый PersonRepository .
@Table("people")
data class Person(
@Id val id: Int? = null,
val name: String,
val age: Int
)
На самом деле, здесь следует отметить один момент. идентификатор был сделан обнуляемым и предоставил значение по умолчанию null , чтобы позволить Postgres самостоятельно генерировать следующее подходящее значение. Если это не обнуляется и указано значение id , Spring фактически попытается запустить обновление вместо вставки при сохранении. Есть и другие способы обойти это, но я думаю, что этого достаточно.
Этот объект будет сопоставлен с таблицей люди , определенной ниже:
CREATE TABLE people ( id SERIAL PRIMARY KEY, name VARCHAR NOT NULL, age INTEGER NOT NULL );
Видеть все это в действии
Теперь давайте посмотрим, как он на самом деле что-то делает. Ниже приведен некоторый код, который вставляет несколько записей и извлекает их несколькими различными способами:
@SpringBootApplication
class Application : CommandLineRunner {
@Autowired
private lateinit var personRepository: PersonRepository
override fun run(vararg args: String?) {
personRepository.saveAll(
listOf(
Person(name = "Dan Newton", age = 25),
Person(name = "Laura So", age = 23)
)
).log().subscribe()
personRepository.findAll().subscribe { log.info("findAll - $it") }
personRepository.findAllById(Mono.just(1)).subscribe { log.info("findAllById - $it") }
personRepository.findAllByName("Laura So").subscribe { log.info("findAllByName - $it") }
personRepository.findAllByAge(25).subscribe { log.info("findAllByAge - $it") }
}
}
Одна вещь, которую я упомяну об этом коде. Существует вполне реальная вероятность того, что он выполняется без фактической вставки или чтения некоторых записей. Но, если подумать об этом. В этом есть смысл. Реактивные приложения предназначены для асинхронной работы, и поэтому это приложение начало обрабатывать вызовы функций в разных потоках. Без блокировки основного потока эти асинхронные процессы могут никогда не выполняться полностью. По этой причине существуют некоторые Вызовы Thread.sleep в этом коде, но я удалил их из примера, чтобы все было в порядке.
Вывод для выполнения приведенного выше кода будет выглядеть примерно так, как показано ниже:
2019-02-11 09:04:52.294 INFO 13226 --- [main] reactor.Flux.ConcatMap.1 : onSubscribe(FluxConcatMap.ConcatMapImmediate) 2019-02-11 09:04:52.295 INFO 13226 --- [main] reactor.Flux.ConcatMap.1 : request(unbounded) 2019-02-11 09:04:52.572 INFO 13226 --- [actor-tcp-nio-1] reactor.Flux.ConcatMap.1 : onNext(Person(id=35, name=Dan Newton, age=25)) 2019-02-11 09:04:52.591 INFO 13226 --- [actor-tcp-nio-1] reactor.Flux.ConcatMap.1 : onNext(Person(id=36, name=Laura So, age=23)) 2019-02-11 09:04:52.591 INFO 13226 --- [actor-tcp-nio-1] reactor.Flux.ConcatMap.1 : onComplete() 2019-02-11 09:04:54.472 INFO 13226 --- [actor-tcp-nio-2] com.lankydanblog.tutorial.Application : findAll - Person(id=35, name=Dan Newton, age=25) 2019-02-11 09:04:54.473 INFO 13226 --- [actor-tcp-nio-2] com.lankydanblog.tutorial.Application : findAll - Person(id=36, name=Laura So, age=23) 2019-02-11 09:04:54.512 INFO 13226 --- [actor-tcp-nio-4] com.lankydanblog.tutorial.Application : findAllByName - Person(id=36, name=Laura So, age=23) 2019-02-11 09:04:54.524 INFO 13226 --- [actor-tcp-nio-5] com.lankydanblog.tutorial.Application : findAllByAge - Person(id=35, name=Dan Newton, age=25)
Здесь нужно забрать несколько вещей:
при подпискеизапросепроисходят в основном потоке, из которого был вызванПоток. Толькосохранить всевыводит это, так как он включил функциюlog. Добавление этого к другим вызовам привело бы к тому же результату входа в основной поток.- Выполнение, содержащееся в функции подписки, и внутренние шаги
Потокавыполняются в отдельных потоках.
Это далеко не реальное представление о том, как вы будете использовать реактивные потоки в реальном приложении, но, надеюсь, демонстрирует, как их использовать, и дает некоторое представление о том, как они выполняются.
Вывод
В заключение, реактивные потоки появились в некоторых базах данных СУБД благодаря драйверу R2 ODBC и данным Spring, которые создают слой сверху, чтобы сделать все немного аккуратнее. Используя Spring Data R2DBC, мы можем создать соединение с базой данных и начать запрашивать ее без необходимости большого количества кода. Хотя Весна уже многое делает для нас, она могла бы сделать еще больше. В настоящее время он не поддерживает автоматическую настройку Spring Boot. Что немного раздражает. Но я уверен, что скоро кто-нибудь соберется это сделать и сделает все еще лучше, чем есть.
Код, использованный в этом посте, можно найти на моем GitHub .
Если вы сочли этот пост полезным, вы можете подписаться на меня в Твиттере по адресу @LankyDanDev , чтобы быть в курсе моих новых постов.
Оригинал: “https://dev.to/lankydandev/asynchronous-rdbms-access-with-spring-data-r2dbc-2811”