Это четвертая часть серии заметок о реактивном программировании, в которой будет представлено введение в R2DBC и описано, как мы можем использовать Spring Data R2DBC для создания полностью реактивного приложения.
1. ЧТО ТАКОЕ R2DBC?
Если вы еще не знакомы с реактивным программированием и реактивными потоками, я рекомендую вам сначала прочитать введение в реактивное программирование, в котором описывается мотивация, лежащая в основе этой парадигмы программирования.
При разработке реактивного приложения, которое должно включать доступ к реляционной базе данных, JDBC не подходит, поскольку это блокирующий API.
R2DBC означает Reactive Relational Database Connectivity
и предназначен для обеспечения возможности работы с базами данных SQL с использованием полностью реактивного неблокирующего API. Он основан на спецификации Reactive Streams и в первую очередь представляет собой SPI (Service Provider Interface — интерфейс поставщика услуг) для разработчиков драйверов баз данных и авторов клиентских библиотек, то есть не предназначен для использования непосредственно в коде приложения.
На данный момент существуют реализации драйверов для Oracle, Microsoft SQL Server, MySQL, PostgreSQL, H2, MariaDB и Google Cloud Spanner.
2. SPRING DATA R2DBC
Spring Data предлагает клиент R2DBC — Spring Data R2DBC.
Это не полный ORM, как JPA — он не предлагает таких функций, как кеширование или отложенная загрузка. Но он обеспечивает функциональность отображения объектов и абстракцию репозитория.
Чтобы продемонстрировать, как его можно использовать, давайте вернемся к примеру StudentController из предыдущей заметки о WebFlux:
@RestController @RequestMapping("/students") public class StudentController { @Autowired private StudentService studentService; public StudentController() { } @GetMapping("/{id}") public Mono<ResponseEntity<Student>> getStudent(@PathVariable long id) { return studentService.findStudentById(id) .map(ResponseEntity::ok) .defaultIfEmpty(ResponseEntity.notFound().build()); } @GetMapping public Flux<Student> listStudents(@RequestParam(name = "name", required = false) String name) { return studentService.findStudentsByName(name); } @PostMapping public Mono<Student> addNewStudent(@RequestBody Student student) { return studentService.addNewStudent(student); } @PutMapping("/{id}") public Mono<ResponseEntity<Student>> updateStudent(@PathVariable long id, @RequestBody Student student) { return studentService.updateStudent(id, student) .map(ResponseEntity::ok) .defaultIfEmpty(ResponseEntity.notFound().build()); } @DeleteMapping("/{id}") public Mono<ResponseEntity<Void>> deleteStudent(@PathVariable long id) { return studentService.findStudentById(id) .flatMap(s -> studentService.deleteStudent(s) .then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK))) ) .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND)); } }
Этот контроллер содержит несколько различных методов для выполнения действий над учащимися. Мы видим, что он использует StudentService для выполнения этих действий. Теперь мы рассмотрим эту функциональность, лежащую в основе контроллера REST, и то, как мы можем реализовать доступ к базе данных с помощью R2DBC.
2.1 ПРИМЕР РЕАЛИЗАЦИИ
2.1.1 ЗАВИСИМОСТИ
Во-первых, нам нужно добавить в наш проект пару новых зависимостей:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-r2dbc</artifactId> </dependency> <dependency> <groupId>io.r2dbc</groupId> <artifactId>r2dbc-postgresql</artifactId> <scope>runtime</scope> </dependency> ... </dependencies>
Нам нужно включить spring-boot-starter-data-r2dbc, чтобы включить spring-data-r2dbc. В этом примере мы будем использовать базу данных postgresql, поэтому нам нужно добавить r2dbc-postgresql, чтобы получить необходимую реализацию драйвера r2dbc.
2.1.2 КОНФИГУРАЦИЯ БАЗЫ ДАННЫХ
Мы можем либо добавить детали подключения к нашей базе данных в application.properties:
spring.r2dbc.url=r2dbc:postgresql://localhost/studentdb spring.r2dbc.username=user spring.r2dbc.password=secret
или используйте конфигурацию на основе Java:
import io.r2dbc.spi.ConnectionFactories; import io.r2dbc.spi.ConnectionFactory; import io.r2dbc.spi.ConnectionFactoryOptions; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import static io.r2dbc.spi.ConnectionFactoryOptions.*; @Configuration public class R2DBCConfig { @Bean public ConnectionFactory connectionFactory() { return ConnectionFactories.get( ConnectionFactoryOptions.builder() .option(DRIVER, "postgresql") .option(HOST, "localhost") .option(USER, "user") .option(PASSWORD, "secret") .option(DATABASE, "studentdb") .build()); } }
2.1.3 STUDENTSERVICE
Теперь давайте посмотрим на StudentService, который использует StudentController:
@Service public class StudentService { @Autowired private StudentRepository studentRepository; public StudentService() { } public Flux<Student> findStudentsByName(String name) { return (name != null) ? studentRepository.findByName(name) : studentRepository.findAll(); } public Mono<Student> findStudentById(long id) { return studentRepository.findById(id); } public Mono<Student> addNewStudent(Student student) { return studentRepository.save(student); } public Mono<Student> updateStudent(long id, Student student) { return studentRepository.findById(id) .flatMap(s -> { student.setId(s.getId()); return studentRepository.save(student); }); } public Mono<Void> deleteStudent(Student student) { return studentRepository.delete(student); } }
Как видите, он использует StudentRepository для выполнения различных операций с базой данных над учащимися. Итак, теперь давайте взглянем на этот репозиторий.
2.1.4 STUDENTREPOSITORY
StudentRepository — это реализация ReactiveCrudRepository. Это интерфейс из Spring Data R2DBC для общих операций CRUD с использованием типов Project Reactor. Поскольку ReactiveCrudRepository уже содержит определения для большинства методов репозитория, которые мы используем в StudentService (findAll, findById, save и delete), нам нужно объявить следующее:
public interface StudentRepository extends ReactiveCrudRepository<Student, Long> { <span class="token keyword" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; font: inherit; vertical-align: baseline; color: rgb(0, 119, 170);">public</span> Flux<span class="token operator" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; font: inherit; vertical-align: baseline; color: rgb(166, 127, 89); background: rgba(255, 255, 255, 0.5);"><</span>Student<span class="token operator" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; font: inherit; vertical-align: baseline; color: rgb(166, 127, 89); background: rgba(255, 255, 255, 0.5);">></span> <span class="token function" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; font: inherit; vertical-align: baseline;">findByName<span class="token punctuation" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; font: inherit; vertical-align: baseline; color: rgb(153, 153, 153);">(</span></span>String name<span class="token punctuation" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; font: inherit; vertical-align: baseline; color: rgb(153, 153, 153);">)</span><span class="token punctuation" style="box-sizing: border-box; margin: 0px; padding: 0px; border: 0px; font: inherit; vertical-align: baseline; color: rgb(153, 153, 153);">;</span> }
Более сложные запросы также можно определить, добавив аннотацию @Query к методу и указав фактический sql.
Помимо ReactiveCrudRepository, существует также расширение ReactiveSortingRepository, которое предоставляет дополнительные методы для извлечения отсортированных сущностей.
2.1.5 STUDENT
Наконец, давайте посмотрим на реализацию Student:
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.data.annotation.Id; import org.springframework.data.relational.core.mapping.Table; @Data @AllArgsConstructor @NoArgsConstructor @Table public class Student { @Id private Long id; private String name; private String address; }
Несколько замечаний:
-
Идентификатор объекта должен быть аннотирован аннотацией Spring Data @Id.
-
Аннотации @Table не обязательны, но ее добавление позволяет сканеру пути к классам находить и предварительно обрабатывать объекты для извлечения связанных метаданных. Если вы не добавите его, это произойдет при первом сохранении объекта, что может немного отрицательно повлиять на производительность.
-
Lombok рекомендуется использовать, чтобы избежать шаблонного кода.
-
Есть также некоторые другие рекомендации для обеспечения оптимальной производительности, вы можете найти подробности в справочной документации.
2.1.6 ДРУГИЕ ВАРИАНТЫ ЗАПРОСОВ
Вместо использования репозитория вы можете выполнить инструкцию SQL напрямую, используя DatabaseClient.
Например, чтобы получить всех студентов:
public Flux<Student> findAll() { DatabaseClient client = DatabaseClient.create(connectionFactory); return client.sql("select * from student") .map(row -> new Student(row.get("id", Long.class), row.get("name", String.class), row.get("address", String.class))).all(); }
Также можно использовать R2dbcEntityTemplate для выполнения операций с сущностями. Например:
@Autowired private R2dbcEntityTemplate template; public Flux<Student> findAll() { return template.select(Student.class).all(); } public Mono<Void> delete(Student student) { return template.delete(student).then(); }
2.2 ДРУГИЕ ОСОБЕННОСТИ
2.2.1 ОПТИМИСТИЧЕСКАЯ БЛОКИРОВКА
Подобно JPA, можно применить аннотацию @Version
на уровне поля, чтобы гарантировать, что обновления применяются только к строкам с соответствующей версией — если версия не соответствует, генерируется исключение OptimisticLockingFailureException.
2.2.2 ТРАНЗАКЦИИ
Spring поддерживает управление реактивными транзакциями через SPI ReactiveTransactionManager. Аннотации @Transactional
можно наносить на реактивных методах возвращающихся типов Publisher и программное управление транзакциями может быть применено с использованием TransactionalOperator.
2.2.3 РЕАКТИВНЫЕ БИБЛИОТЕКИ
Как и WebFlux, Spring Data R2DBC требует Project Reactor в качестве основной зависимости, но он совместим с другими реактивными библиотеками, реализующими спецификацию Reactive Streams. Репозитории существуют также для RxJava2 и RxJava3 (см. обзор пакета).
2.2.4 ПУЛ СОЕДИНЕНИЙ
Для пула соединений доступна библиотека под названием r2dbc-pool. Подробнее о том, как его использовать, читайте здесь.
3. ГОТОВНОСТЬ ДЛЯ ПРОДАКШН
R2DBC — все еще довольно новая технология. Последние версии релиза на данный момент:
-
Спецификация R2DBC: 0.8.5
-
Spring Data R2DBC: 1.3.1
-
r2dbc-postgresql: 0.8.8
-
r2dbc-pool: 0.8.7
Прежде чем принять решение об их использовании для вашего приложения, конечно, рекомендуется более внимательно изучить текущее состояние драйвера базы данных и реализаций пула в соответствии с вашими требованиями. Есть некоторые нерешенные проблемы, которые могут помешать вам сделать этот шаг на данный момент, но улучшения продолжаются.
4. ПОДВОДЯ ИТОГ…
Это сообщение в блоге продемонстрировало, как Spring Data R2DBC можно использовать в приложении WebFlux. Таким образом, мы создали полностью реактивное приложение и подошли к концу этой серии статей о реактивном программировании.
Еще одна интересная инициатива, о которой стоит упомянуть, — это Project Loom. Это проект OpenJDK, который стартовал еще в 2017 году и направлен на обеспечение облегченного параллелизма, включая новый тип потоков Java, которые напрямую не соответствуют выделенным потокам ОС. Такой тип виртуальных потоков было бы намного дешевле создавать и блокировать.
Как вы, возможно, помните из первого сообщения в блоге, ключевыми факторами, лежащими в основе модели реактивного программирования, являются следующие:
-
отход от потока на модель запроса и может обрабатывать больше запросов с небольшим количеством потоков
-
предотвращение блокировки потоков при ожидании завершения операций ввода-вывода
-
упрощение параллельных вызовов
-
поддержка «обратного давления», давая клиенту возможность сообщить серверу, с какой нагрузкой он может справиться
Project Loom кажется очень многообещающим, когда дело доходит до помощи с первыми двумя элементами в этом списке — тогда об этом позаботится сама JVM без какой-либо дополнительной инфраструктуры.
Еще не решено, когда изменения будут внесены в официальный выпуск Java, но двоичные файлы раннего доступа доступны для загрузки.
ССЫЛКИ
ссылка на оригинал статьи https://habr.com/ru/post/565060/
Добавить комментарий