[Java] Реактивное программирование со Spring, часть 4 R2DBC (перевод)

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
30-Июн-2021 16:35

Это четвертая часть серии заметок о реактивном программировании, в которой будет представлено введение в 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 R2DBCSpring 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 STUDENTREPOSITORYStudentRepository - это реализация 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, но двоичные файлы раннего доступа доступны для загрузки.ССЫЛКИR2DBCSpring Data R2DBC Reference Documentationr2dbc-postgresqlr2dbc-poolProject LoomGoing inside Java’s Project Loom and virtual threads
===========
Источник:
habr.com
===========

===========
Автор оригинала: ANNA ERIKSSON
===========
Похожие новости: Теги для поиска: #_java, #_spring_boot_2, #_reactive_programming, #_r2dbc, #_java
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 22-Ноя 12:09
Часовой пояс: UTC + 5