Тестирование и отладка
После реализации протестируйте, чтобы убедиться в правильной работе уникальности.
Запустите проект:
Правой кнопкой на Main.java → Run 'Main.main()'.
В консоли увидите вывод уникальных авторов. Убедитесь, что дубликаты авторов не появляются в списке.
Проверьте уникальность:
Добавьте две книги с одним автором — в Set должно быть только одно имя.
Попробуйте добавить null как автора (если поле author позволяет) — проверьте поведение Set (HashSet позволит один null).
Отладка:
Установите breakpoint в методе addBook перед authors.add() и после — шагайте (F8) и смотрите, меняется ли размер Set.
Если ошибки: NullPointerException (если Set не инициализировано), ArrayIndexOutOfBoundsException (если массив переполнен) — добавьте проверку на размер.
В консоли выводите сообщения, например, если add вернул false: "Автор [name] уже существует".
Эксперименты:
Измените реализацию Set на LinkedHashSet — проверьте, сохраняется ли порядок добавления авторов.
Попробуйте TreeSet — добавьте Comparator, если нужно сортировать авторов по алфавиту (TreeSet требует Comparable для элементов).
Полезные советы для новичков
Инициализация Set: Всегда инициализируйте в конструкторе класса (authors = new HashSet<>();), чтобы избежать NullPointerException.
Проверка возвращаемого значения: Используйте boolean от add/remove для логики (например, уведомление о дубликате).
Custom уникальность: Если уникальность по полю (как author), убедитесь, что equals/hashCode в Book учитывают только author, если нужно (но для Set это не требуется, так как String имеет правильные методы).
Расширение проекта: Подумайте, как добавить метод для проверки, существует ли автор перед добавлением книги (используйте contains).
Производительность: Для малого количества авторов (сотни) любая реализация подойдет; для больших — HashSet fastest.
Практическое задание
Задача 1: Добавьте в Library метод isAuthorUnique(String authorName), который использует contains для проверки, есть ли автор в Set.
Задача 2: В addBook перед добавлением автора проверьте с помощью contains, и если он новый, выведите сообщение "Новый автор добавлен".
Задача 3: Создайте 5-6 книг с 3-4 уникальными авторами (некоторые повторяются), добавьте их, выведите авторов и убедитесь в уникальности.
Реализуйте эти задачи самостоятельно, следуя шагам урока. Это закрепит работу с Set в контексте проекта.
#Java #для_новичков #beginner #Collections #Set
После реализации протестируйте, чтобы убедиться в правильной работе уникальности.
Запустите проект:
Правой кнопкой на Main.java → Run 'Main.main()'.
В консоли увидите вывод уникальных авторов. Убедитесь, что дубликаты авторов не появляются в списке.
Проверьте уникальность:
Добавьте две книги с одним автором — в Set должно быть только одно имя.
Попробуйте добавить null как автора (если поле author позволяет) — проверьте поведение Set (HashSet позволит один null).
Отладка:
Установите breakpoint в методе addBook перед authors.add() и после — шагайте (F8) и смотрите, меняется ли размер Set.
Если ошибки: NullPointerException (если Set не инициализировано), ArrayIndexOutOfBoundsException (если массив переполнен) — добавьте проверку на размер.
В консоли выводите сообщения, например, если add вернул false: "Автор [name] уже существует".
Эксперименты:
Измените реализацию Set на LinkedHashSet — проверьте, сохраняется ли порядок добавления авторов.
Попробуйте TreeSet — добавьте Comparator, если нужно сортировать авторов по алфавиту (TreeSet требует Comparable для элементов).
Полезные советы для новичков
Инициализация Set: Всегда инициализируйте в конструкторе класса (authors = new HashSet<>();), чтобы избежать NullPointerException.
Проверка возвращаемого значения: Используйте boolean от add/remove для логики (например, уведомление о дубликате).
Custom уникальность: Если уникальность по полю (как author), убедитесь, что equals/hashCode в Book учитывают только author, если нужно (но для Set это не требуется, так как String имеет правильные методы).
Расширение проекта: Подумайте, как добавить метод для проверки, существует ли автор перед добавлением книги (используйте contains).
Производительность: Для малого количества авторов (сотни) любая реализация подойдет; для больших — HashSet fastest.
Практическое задание
Задача 1: Добавьте в Library метод isAuthorUnique(String authorName), который использует contains для проверки, есть ли автор в Set.
Задача 2: В addBook перед добавлением автора проверьте с помощью contains, и если он новый, выведите сообщение "Новый автор добавлен".
Задача 3: Создайте 5-6 книг с 3-4 уникальными авторами (некоторые повторяются), добавьте их, выведите авторов и убедитесь в уникальности.
Реализуйте эти задачи самостоятельно, следуя шагам урока. Это закрепит работу с Set в контексте проекта.
#Java #для_новичков #beginner #Collections #Set
👍2
  Реактивное программирование
Введение в Spring WebFlux
Spring WebFlux — это часть Spring Framework, предназначенная для создания неблокирующих, асинхронных веб-приложений. Он основан на Project Reactor и Reactive Streams API, что позволяет использовать Mono и Flux прямо в коде сервера. Это не замена Spring MVC (традиционному веб-фреймворку), а альтернатива для сценариев с высокой нагрузкой: микросервисы, реальное время, стриминг. Почему это новый подход? В традиционном веб (как Servlet с потоками на запрос) под пиковой нагрузкой сервер исчерпывает ресурсы — каждый запрос занимает поток, который висит в ожидании IO. WebFlux меняет это: использует event-loop (цикл обработки событий), где один поток обслуживает тысячи подключений, реагируя на события асинхронно. Это экономит CPU и память, делая приложения более отзывчивыми и масштабируемыми.
Почему Spring WebFlux: связь с реактивным мышлением
WebFlux воплощает принципы, которые мы разбирали: push-модель, где данные отправляются по готовности; обратное давление для контроля темпа; операторы для трансформаций. В экосистеме Spring он интегрируется с другими компонентами: Spring Boot для быстрого старта, Spring Data Reactive для баз данных (как R2DBC), Spring Security Reactive для безопасности. Ключевой выигрыш — для IO-bound задач: запросы к БД, API или файлам идут асинхронно, без блокировок. Если ваш сервис ждёт внешних ответов 90% времени, WebFlux высвобождает ресурсы, позволяя обрабатывать в 10-100 раз больше запросов на том же железе.
Чтобы начать: добавьте spring-boot-starter-webflux в зависимости (Maven/Gradle). Spring Boot автоматически настроит Reactor Netty как сервер (неблокирующий HTTP на базе Netty). Нет нужды в Tomcat — всё реактивно.
Ключевые компоненты Spring WebFlux
WebFlux предлагает два стиля разработки: аннотированный (похож на Spring MVC) и функциональный (роутеры как в Express.js). Оба используют Mono/Flux для ответов.
- Аннотированные контроллеры: @RestController с методами, возвращающими Mono или Flux.
Пример простого GET:
- Функциональные роутеры: Для маршрутизации без контроллеров.
Пример:
- WebClient: реактивный клиент для исходящих запросов, интегрируется seamlessly.
Ещё фишки: поддержка WebSockets для bidirectional стриминга, Server-Sent Events для push-уведомлений, интеграция с Schedulers для распределения задач.
Практические советы и подводные камни
- Миграция: Начните с аннотированных контроллеров — синтаксис похож на MVC, но возвращайте Mono/Flux.
- Тестирование: WebTestClient вместо MockMvc — асинхронные тесты с StepVerifier (из Reactor).
- Камень: Блокирующий код в контроллерах (JDBC, sleep) сломает асинхронность — используйте reactive драйверы (R2DBC) и publishOn(Schedulers.boundedElastic()).
- Производительность: Под нагрузкой мониторьте с Micrometer — WebFlux даёт метрики из коробки.
В реальной жизни: Netflix использует похожий стек для стриминга, где WebFlux обрабатывает миллионы подключений.
#Java #middle #Reactor #WebFlux
Введение в Spring WebFlux
Spring WebFlux — это часть Spring Framework, предназначенная для создания неблокирующих, асинхронных веб-приложений. Он основан на Project Reactor и Reactive Streams API, что позволяет использовать Mono и Flux прямо в коде сервера. Это не замена Spring MVC (традиционному веб-фреймворку), а альтернатива для сценариев с высокой нагрузкой: микросервисы, реальное время, стриминг. Почему это новый подход? В традиционном веб (как Servlet с потоками на запрос) под пиковой нагрузкой сервер исчерпывает ресурсы — каждый запрос занимает поток, который висит в ожидании IO. WebFlux меняет это: использует event-loop (цикл обработки событий), где один поток обслуживает тысячи подключений, реагируя на события асинхронно. Это экономит CPU и память, делая приложения более отзывчивыми и масштабируемыми.
Почему Spring WebFlux: связь с реактивным мышлением
WebFlux воплощает принципы, которые мы разбирали: push-модель, где данные отправляются по готовности; обратное давление для контроля темпа; операторы для трансформаций. В экосистеме Spring он интегрируется с другими компонентами: Spring Boot для быстрого старта, Spring Data Reactive для баз данных (как R2DBC), Spring Security Reactive для безопасности. Ключевой выигрыш — для IO-bound задач: запросы к БД, API или файлам идут асинхронно, без блокировок. Если ваш сервис ждёт внешних ответов 90% времени, WebFlux высвобождает ресурсы, позволяя обрабатывать в 10-100 раз больше запросов на том же железе.
Чтобы начать: добавьте spring-boot-starter-webflux в зависимости (Maven/Gradle). Spring Boot автоматически настроит Reactor Netty как сервер (неблокирующий HTTP на базе Netty). Нет нужды в Tomcat — всё реактивно.
Ключевые компоненты Spring WebFlux
WebFlux предлагает два стиля разработки: аннотированный (похож на Spring MVC) и функциональный (роутеры как в Express.js). Оба используют Mono/Flux для ответов.
- Аннотированные контроллеры: @RestController с методами, возвращающими Mono или Flux.
Пример простого GET:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
@RestController
public class HelloController {
@GetMapping("/hello")
public Mono<String> hello() {
return Mono.just("Привет из WebFlux!"); // Асинхронный ответ
}
}
Здесь метод возвращает Mono — сервер не блокируется, ответ "течёт" асинхронно. Для Flux: стриминг данных, как SSE (сервер-сент события).
- Функциональные роутеры: Для маршрутизации без контроллеров.
Пример:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
@Configuration
public class RouterConfig {
@Bean
public RouterFunction<ServerResponse> routes() {
return RouterFunctions.route()
.GET("/hello", request -> ServerResponse.ok().bodyValue("Привет из роутера!"))
.build();
}
}
Это декларативно: опишите маршруты, обработчики возвращают ServerResponse с Mono/Flux.
- WebClient: реактивный клиент для исходящих запросов, интегрируется seamlessly.
Ещё фишки: поддержка WebSockets для bidirectional стриминга, Server-Sent Events для push-уведомлений, интеграция с Schedulers для распределения задач.
Практические советы и подводные камни
- Миграция: Начните с аннотированных контроллеров — синтаксис похож на MVC, но возвращайте Mono/Flux.
- Тестирование: WebTestClient вместо MockMvc — асинхронные тесты с StepVerifier (из Reactor).
- Камень: Блокирующий код в контроллерах (JDBC, sleep) сломает асинхронность — используйте reactive драйверы (R2DBC) и publishOn(Schedulers.boundedElastic()).
- Производительность: Под нагрузкой мониторьте с Micrometer — WebFlux даёт метрики из коробки.
В реальной жизни: Netflix использует похожий стек для стриминга, где WebFlux обрабатывает миллионы подключений.
#Java #middle #Reactor #WebFlux
👍3
  Раздел 6. Коллекции в Java
Глава 4. Queue и Deque
Интерфейс Queue. Очередь как структура FIFO. Методы offer, poll, peek
Интерфейс Queue<E> — часть Java Collections Framework (JCF) из пакета java.util, предназначенная для хранения элементов, которые обрабатываются в определенном порядке. Queue моделирует очередь, где элементы добавляются в конец (tail) и извлекаются из начала (head). Основной принцип для большинства реализаций — FIFO, хотя есть исключения, например, приоритетные очереди.
Основные характеристики Queue:
FIFO (First-In-First-Out): Элементы обрабатываются в порядке добавления, как очередь в магазине.
Уникальность: Не гарантируется — дубликаты разрешены (зависит от реализации).
Порядок: Определен структурой (обычно порядок добавления или приоритет).
Big O: Зависит от реализации (O(1) для LinkedList, O(log n) для PriorityQueue).
Null элементы: Зависит от реализации (LinkedList позволяет, PriorityQueue — нет).
Где используется:
Очереди задач (например, обработка запросов в сервере).
Буферы (например, поток ввода-вывода).
Алгоритмы (например, обход графа в ширину — BFS).
Queue<E> расширяет Collection<E>, добавляя методы для работы с очередью.
Основные реализации:
LinkedList (как Queue), ArrayDeque и PriorityQueue.
FIFO: Принцип "первым вошел — первым вышел"
FIFO — это структура данных, где:
Элементы добавляются в конец очереди (enqueue).
Элементы извлекаются из начала (dequeue).
Аналогия: Люди в очереди в кассу — первый в очереди обслуживается первым.
Пример FIFO в жизни: Очередь сообщений в чате обрабатывается в порядке отправки.
Нюанс: PriorityQueue нарушает FIFO, сортируя по приоритету (рассмотрим в следующем уроке).
Основные методы Queue: offer, poll, peek
Queue предоставляет методы для работы с очередью, которые отличаются от add/remove Collection тем, как обрабатывают ограничения (например, заполненность).
offer(E e):
Описание: Добавляет элемент в конец очереди.
Возвращаемое значение: true, если добавлен; false, если очередь полная (для bounded очередей, например, с фиксированным размером).
Исключения: NullPointerException, если null в реализациях, не допускающих null (PriorityQueue).
Big O: O(1) для LinkedList/ArrayDeque, O(log n) для PriorityQueue (перестройка кучи).
poll():
Описание: Извлекает и удаляет элемент из начала очереди.
Возвращаемое значение: Элемент (E) или null, если очередь пуста.
Исключения: Нет (безопаснее, чем remove()).
Big O: O(1) для LinkedList/ArrayDeque, O(log n) для PriorityQueue.
peek():
Описание: Возвращает элемент из начала очереди без удаления.
Возвращаемое значение: Элемент (E) или null, если очередь пуста.
Исключения: Нет.
Big O: O(1) для всех реализаций.
Альтернативы (с исключениями):
add(E e): Как offer, но кидает IllegalStateException при переполнении.
remove(): Как poll, но кидает NoSuchElementException, если пусто.
element(): Как peek, но кидает NoSuchElementException, если пусто.
Нюанс: offer/poll/peek предпочтительнее для очередей, так как безопаснее.
#Java #для_новичков #beginner #Collections #Queue
Глава 4. Queue и Deque
Интерфейс Queue. Очередь как структура FIFO. Методы offer, poll, peek
Интерфейс Queue<E> — часть Java Collections Framework (JCF) из пакета java.util, предназначенная для хранения элементов, которые обрабатываются в определенном порядке. Queue моделирует очередь, где элементы добавляются в конец (tail) и извлекаются из начала (head). Основной принцип для большинства реализаций — FIFO, хотя есть исключения, например, приоритетные очереди.
Основные характеристики Queue:
FIFO (First-In-First-Out): Элементы обрабатываются в порядке добавления, как очередь в магазине.
Уникальность: Не гарантируется — дубликаты разрешены (зависит от реализации).
Порядок: Определен структурой (обычно порядок добавления или приоритет).
Big O: Зависит от реализации (O(1) для LinkedList, O(log n) для PriorityQueue).
Null элементы: Зависит от реализации (LinkedList позволяет, PriorityQueue — нет).
Где используется:
Очереди задач (например, обработка запросов в сервере).
Буферы (например, поток ввода-вывода).
Алгоритмы (например, обход графа в ширину — BFS).
Queue<E> расширяет Collection<E>, добавляя методы для работы с очередью.
Основные реализации:
LinkedList (как Queue), ArrayDeque и PriorityQueue.
FIFO: Принцип "первым вошел — первым вышел"
FIFO — это структура данных, где:
Элементы добавляются в конец очереди (enqueue).
Элементы извлекаются из начала (dequeue).
Аналогия: Люди в очереди в кассу — первый в очереди обслуживается первым.
Пример FIFO в жизни: Очередь сообщений в чате обрабатывается в порядке отправки.
Нюанс: PriorityQueue нарушает FIFO, сортируя по приоритету (рассмотрим в следующем уроке).
Основные методы Queue: offer, poll, peek
Queue предоставляет методы для работы с очередью, которые отличаются от add/remove Collection тем, как обрабатывают ограничения (например, заполненность).
offer(E e):
Описание: Добавляет элемент в конец очереди.
Возвращаемое значение: true, если добавлен; false, если очередь полная (для bounded очередей, например, с фиксированным размером).
Исключения: NullPointerException, если null в реализациях, не допускающих null (PriorityQueue).
Big O: O(1) для LinkedList/ArrayDeque, O(log n) для PriorityQueue (перестройка кучи).
poll():
Описание: Извлекает и удаляет элемент из начала очереди.
Возвращаемое значение: Элемент (E) или null, если очередь пуста.
Исключения: Нет (безопаснее, чем remove()).
Big O: O(1) для LinkedList/ArrayDeque, O(log n) для PriorityQueue.
peek():
Описание: Возвращает элемент из начала очереди без удаления.
Возвращаемое значение: Элемент (E) или null, если очередь пуста.
Исключения: Нет.
Big O: O(1) для всех реализаций.
Альтернативы (с исключениями):
add(E e): Как offer, но кидает IllegalStateException при переполнении.
remove(): Как poll, но кидает NoSuchElementException, если пусто.
element(): Как peek, но кидает NoSuchElementException, если пусто.
Нюанс: offer/poll/peek предпочтительнее для очередей, так как безопаснее.
#Java #для_новичков #beginner #Collections #Queue
👍2
  Примеры использования методов
LinkedList как Queue (FIFO):
PriorityQueue (не FIFO, по приоритету):
Все нюансы методов
offer:
Для bounded очередей (с ограничением размера, например, ArrayBlockingQueue) возвращает false, если полная.
Null: LinkedList позволяет, PriorityQueue — NPE.
Custom объекты: Для PriorityQueue должны быть Comparable или нужен Comparator.
poll:
Безопасно для пустой очереди — просто null.
В PriorityQueue извлекает минимальный элемент (или по Comparator).
peek:
Не меняет очередь — только просмотр.
Null при пустой очереди, безопасно.
Ошибки:
NullPointerException: В PriorityQueue при null.
ClassCastException: В PriorityQueue, если элементы не Comparable.
ConcurrentModificationException: При модификации во время итерации (используйте poll вместо Iterator.remove()).
Thread-safety:
LinkedList, PriorityQueue не thread-safe. Используйте BlockingQueue (например, ArrayBlockingQueue) для многопоточности.
Collections.synchronizedCollection() — простой вариант синхронизации.
Производительность:
LinkedList: O(1) для всех методов (двусвязный список).
PriorityQueue: O(log n) для offer/poll (перестройка кучи), O(1) для peek.
ArrayDeque: O(1) для всех (рассмотрим в следующем уроке).
Полезные советы для новичков
Используйте offer/poll/peek: Безопаснее, чем add/remove/element.
LinkedList как Queue: Универсально для FIFO, но больше памяти, чем ArrayDeque.
Custom классы в PriorityQueue: Реализуйте Comparable или передайте Comparator.
Проверка пустоты: queue.isEmpty() перед poll/peek не нужна — null безопасен.
Итерация: For-each для чтения, но не модифицируйте очередь.
#Java #для_новичков #beginner #Collections #Queue
LinkedList как Queue (FIFO):
javaimport java.util.LinkedList;
import java.util.Queue;
public class Main {
public static void main(String[] args) {
Queue<String> queue = new LinkedList<>();
// Добавление
queue.offer("Задача 1");
queue.offer("Задача 2");
System.out.println(queue); // [Задача 1, Задача 2]
// Просмотр
System.out.println(queue.peek()); // Задача 1 (без удаления)
System.out.println(queue); // [Задача 1, Задача 2]
// Извлечение
String task = queue.poll(); // Задача 1
System.out.println(task); // Задача 1
System.out.println(queue); // [Задача 2]
// Пустая очередь
queue.poll(); // Задача 2
System.out.println(queue.poll()); // null (без исключения)
}
}
Вывод: Показывает FIFO — элементы извлекаются в порядке добавления, null при пустой очереди.
PriorityQueue (не FIFO, по приоритету):
javaimport java.util.PriorityQueue;
import java.util.Queue;
public class Main {
public static void main(String[] args) {
Queue<Integer> queue = new PriorityQueue<>();
queue.offer(3);
queue.offer(1);
queue.offer(2);
System.out.println(queue); // [1, 3, 2] — минимальный в начале
System.out.println(queue.peek()); // 1
System.out.println(queue.poll()); // 1
System.out.println(queue); // [2, 3]
}
}
Нюанс: PriorityQueue сортирует по натуральному порядку (или Comparator), не FIFO.
Все нюансы методов
offer:
Для bounded очередей (с ограничением размера, например, ArrayBlockingQueue) возвращает false, если полная.
Null: LinkedList позволяет, PriorityQueue — NPE.
Custom объекты: Для PriorityQueue должны быть Comparable или нужен Comparator.
poll:
Безопасно для пустой очереди — просто null.
В PriorityQueue извлекает минимальный элемент (или по Comparator).
peek:
Не меняет очередь — только просмотр.
Null при пустой очереди, безопасно.
Ошибки:
NullPointerException: В PriorityQueue при null.
ClassCastException: В PriorityQueue, если элементы не Comparable.
ConcurrentModificationException: При модификации во время итерации (используйте poll вместо Iterator.remove()).
Thread-safety:
LinkedList, PriorityQueue не thread-safe. Используйте BlockingQueue (например, ArrayBlockingQueue) для многопоточности.
Collections.synchronizedCollection() — простой вариант синхронизации.
Производительность:
LinkedList: O(1) для всех методов (двусвязный список).
PriorityQueue: O(log n) для offer/poll (перестройка кучи), O(1) для peek.
ArrayDeque: O(1) для всех (рассмотрим в следующем уроке).
Полезные советы для новичков
Используйте offer/poll/peek: Безопаснее, чем add/remove/element.
LinkedList как Queue: Универсально для FIFO, но больше памяти, чем ArrayDeque.
Custom классы в PriorityQueue: Реализуйте Comparable или передайте Comparator.
Проверка пустоты: queue.isEmpty() перед poll/peek не нужна — null безопасен.
Итерация: For-each для чтения, но не модифицируйте очередь.
#Java #для_новичков #beginner #Collections #Queue
👍3
  Реактивное программирование
Простой REST-контроллер с Mono и Flux в Spring WebFlux
Spring WebFlux работает на Spring Boot — это упрощает запуск.
Создайте новый проект в IDE (IntelliJ, Eclipse) или через spring.io/initializr с зависимостями:
- Spring Reactive Web (для WebFlux).
- Spring Boot Starter WebFlux.
В pom.xml (Maven) это выглядит так:
Запустите приложение с @SpringBootApplication в главном классе. По умолчанию сервер — Reactor Netty (неблокирующий HTTP-сервер), порт 8080. Нет нужды в Tomcat — всё асинхронно. Если у вас уже есть Spring MVC, исключите starter-web, чтобы избежать конфликтов.
Теперь к контроллерам: в WebFlux они аннотированы @RestController (как в MVC), но методы возвращают Mono или Flux. Это значит: контроллер не блокирует поток — он возвращает "реактивный объект", а фреймворк сам подпишется и отправит ответ по мере готовности. Это решает боли блокировок: пока данные готовятся (например, запрос в БД), поток свободен для других запросов.
Mono в контроллере: для одиночных ответов
Mono — это поток для нуля или одного элемента, идеален для типичных REST-операций: GET одного ресурса, POST с созданием, DELETE с подтверждением. В контроллере метод возвращает Mono<T>, где T — ваш DTO или простая строка. Фреймворк автоматически сериализует его в JSON (с Jackson) и отправляет, когда элемент готов.
Простой пример: контроллер для приветствия.
Почему Mono лучше Object в MVC? В MVC метод ждёт выполнения (блокирует поток), здесь — нет. Под нагрузкой: тысячи /greet запросов — WebFlux использует event-loop (один поток на все), Reactor распределяет через Schedulers.
Если внутри Mono — запрос к сервису:
В контроллере:
Это цепочка: контроллер возвращает Mono от сервиса, фреймворк ждёт готовности без блокировки. Если ошибка в сервисе — onError преобразуется в HTTP 500, но вы можете настроить с @ExceptionHandler.
Расширим: обработка параметров запроса (@RequestParam) и тела (@RequestBody).
Для POST:
#Java #middle #Reactor #WebFlux #Mono #Flux
Простой REST-контроллер с Mono и Flux в Spring WebFlux
Spring WebFlux работает на Spring Boot — это упрощает запуск.
Создайте новый проект в IDE (IntelliJ, Eclipse) или через spring.io/initializr с зависимостями:
- Spring Reactive Web (для WebFlux).
- Spring Boot Starter WebFlux.
В pom.xml (Maven) это выглядит так:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
Для Gradle: implementation 'org.springframework.boot:spring-boot-starter-webflux'
Запустите приложение с @SpringBootApplication в главном классе. По умолчанию сервер — Reactor Netty (неблокирующий HTTP-сервер), порт 8080. Нет нужды в Tomcat — всё асинхронно. Если у вас уже есть Spring MVC, исключите starter-web, чтобы избежать конфликтов.
Теперь к контроллерам: в WebFlux они аннотированы @RestController (как в MVC), но методы возвращают Mono или Flux. Это значит: контроллер не блокирует поток — он возвращает "реактивный объект", а фреймворк сам подпишется и отправит ответ по мере готовности. Это решает боли блокировок: пока данные готовятся (например, запрос в БД), поток свободен для других запросов.
Mono в контроллере: для одиночных ответов
Mono — это поток для нуля или одного элемента, идеален для типичных REST-операций: GET одного ресурса, POST с созданием, DELETE с подтверждением. В контроллере метод возвращает Mono<T>, где T — ваш DTO или простая строка. Фреймворк автоматически сериализует его в JSON (с Jackson) и отправляет, когда элемент готов.
Простой пример: контроллер для приветствия.
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
@RestController
public class GreetingController {
@GetMapping("/greet/{name}")
public Mono<String> greet(@PathVariable String name) {
return Mono.just("Привет, " + name + "! Добро пожаловать в реактивный мир.");
}
}
Здесь @GetMapping — аннотация для маршрута, @PathVariable — параметр из URL. Метод возвращает Mono.just — простой синхронный элемент. Но под капотом: WebFlux подпишется на Mono, и когда значение готово (сразу), отправит HTTP 200 с телом. Если бы внутри был асинхронный вызов (например, Mono.delay(Duration.ofSeconds(2)).map(ignore -> "Задержанный привет")), сервер не заблокируется — запрос обработается асинхронно.
Почему Mono лучше Object в MVC? В MVC метод ждёт выполнения (блокирует поток), здесь — нет. Под нагрузкой: тысячи /greet запросов — WebFlux использует event-loop (один поток на все), Reactor распределяет через Schedulers.
Если внутри Mono — запрос к сервису:
@Service
public class UserService {
public Mono<User> findUserById(Long id) {
return Mono.fromCallable(() -> /* симуляция БД */ new User(id, "Имя")); // Асинхронно
}
}
В контроллере:
@GetMapping("/user/{id}")
public Mono<User> getUser(@PathVariable Long id) {
    return userService.findUserById(id);
}Это цепочка: контроллер возвращает Mono от сервиса, фреймворк ждёт готовности без блокировки. Если ошибка в сервисе — onError преобразуется в HTTP 500, но вы можете настроить с @ExceptionHandler.
Расширим: обработка параметров запроса (@RequestParam) и тела (@RequestBody).
Для POST:
@PostMapping("/create-user")
public Mono<User> createUser(@RequestBody Mono<UserRequest> requestMono) {
    return requestMono.flatMap(req -> {
        // Асинхронная логика: создать пользователя
        return userService.createUser(req.getName()).map(saved -> new User(saved.getId(), saved.getName()));
    });
}
Здесь @RequestBody — Mono, потому что тело может приходить асинхронно (большие данные). flatMap — для цепочки (из поста 8), чтобы "развернуть" подпоток.#Java #middle #Reactor #WebFlux #Mono #Flux
👍2
  Flux в контроллере: для коллекций и стриминга
Flux — для нуля, одного или многих элементов, подходит для GET списков, пагинации или реального времени (стриминг событий). Возвращайте Flux<T>, и WebFlux отправит ответ как JSON-массив (для конечного) или text/event-stream для SSE (сервер-сент событий).
Простой пример: список элементов.
Фреймворк соберёт Flux в JSON-массив и отправит одним ответом.
Но если Flux асинхронный:
Ответ будет стриминговым: клиенту придут данные по мере готовности (chunked transfer).
Для явного SSE добавьте produces = MediaType.TEXT_EVENT_STREAM_VALUE:
Расширим: пагинация с @RequestParam.
Для POST с Flux телом (batch-операции):
Обработка ошибок и валидация в контроллерах
Ошибки — часть жизни: в WebFlux используйте @ExceptionHandler в контроллере или глобально (@ControllerAdvice).
Пример:
Для валидации: @Validated на контроллере, @Valid на @RequestBody. Ошибки валидации — WebExchangeBindException, обработайте в handler.
Глобально: @ControllerAdvice с @ExceptionHandler для централизованной обработки. Это интегрируется с onErrorResume/retry: в сервисе добавьте, и контроллер получит восстановленный поток.
Практические советы и подводные камни
- Тестирование: Используйте WebTestClient — webTestClient.get().uri("/greet/Name").exchange().expectStatus().isOk().expectBody(String.class).isEqualTo("Привет, Name!");
- Schedulers: Если в сервисе blocking код, добавьте .publishOn(Schedulers.boundedElastic()) перед возвратом в контроллер.
- Проблема: Возврат Flux без produces = TEXT_EVENT_STREAM — соберётся в массив, но под большой нагрузкой рискуете буфером; для стриминга укажите media type.
- Оптимизация: Для больших ответов используйте Flux<DataBuffer> — стриминг байтов без сериализации в память.
- Интеграция: С Spring Security Reactive — фильтры асинхронны, не блокируют.
#Java #middle #Reactor #WebFlux #Mono #Flux
Flux — для нуля, одного или многих элементов, подходит для GET списков, пагинации или реального времени (стриминг событий). Возвращайте Flux<T>, и WebFlux отправит ответ как JSON-массив (для конечного) или text/event-stream для SSE (сервер-сент событий).
Простой пример: список элементов.
@GetMapping("/users")
public Flux<User> getAllUsers() {
    return Flux.fromIterable(List.of(new User(1L, "Алиса"), new User(2L, "Боб")));
}Фреймворк соберёт Flux в JSON-массив и отправит одним ответом.
Но если Flux асинхронный:
public Flux<User> findAllUsers() {
    return Flux.range(1, 5)
        .delayElements(Duration.ofSeconds(1)) // Симуляция задержки
        .map(i -> new User((long) i, "Пользователь " + i));
}Ответ будет стриминговым: клиенту придут данные по мере готовности (chunked transfer).
Для явного SSE добавьте produces = MediaType.TEXT_EVENT_STREAM_VALUE:
@GetMapping(value = "/stream-users", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<User> streamUsers() {
return findAllUsers();
}
Теперь ответ — SSE: каждый элемент как event: data: {"id":1,"name":"Пользователь 1"}\n\n. Клиент (браузер или WebClient) может реагировать на каждый по отдельности, без ожидания всего. Это решает боли callback-ада: вместо polling (опроса сервера), сервер толкает данные (push из поста 3).
Расширим: пагинация с @RequestParam.
@GetMapping("/users-paged")
public Flux<User> getPagedUsers(@RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size) {
    return userService.findPaged(page, size); // Сервис возвращает Flux<User>
}Для POST с Flux телом (batch-операции):
@PostMapping("/batch-users")
public Flux<User> createBatch(@RequestBody Flux<UserRequest> requestsFlux) {
    return requestsFlux.flatMap(req -> userService.createUser(req.getName()));
}
flatMap — для параллельной обработки, возвращает Flux сохранённых пользователей.Обработка ошибок и валидация в контроллерах
Ошибки — часть жизни: в WebFlux используйте @ExceptionHandler в контроллере или глобально (@ControllerAdvice).
Пример:
@ExceptionHandler(UserNotFoundException.class)
public Mono<ResponseEntity<String>> handleNotFound(UserNotFoundException ex) {
return Mono.just(ResponseEntity.notFound().build());
}
Для валидации: @Validated на контроллере, @Valid на @RequestBody. Ошибки валидации — WebExchangeBindException, обработайте в handler.
Глобально: @ControllerAdvice с @ExceptionHandler для централизованной обработки. Это интегрируется с onErrorResume/retry: в сервисе добавьте, и контроллер получит восстановленный поток.
Практические советы и подводные камни
- Тестирование: Используйте WebTestClient — webTestClient.get().uri("/greet/Name").exchange().expectStatus().isOk().expectBody(String.class).isEqualTo("Привет, Name!");
- Schedulers: Если в сервисе blocking код, добавьте .publishOn(Schedulers.boundedElastic()) перед возвратом в контроллер.
- Проблема: Возврат Flux без produces = TEXT_EVENT_STREAM — соберётся в массив, но под большой нагрузкой рискуете буфером; для стриминга укажите media type.
- Оптимизация: Для больших ответов используйте Flux<DataBuffer> — стриминг байтов без сериализации в память.
- Интеграция: С Spring Security Reactive — фильтры асинхронны, не блокируют.
#Java #middle #Reactor #WebFlux #Mono #Flux
👍1
  Раздел 6. Коллекции в Java
Глава 4. Queue и Deque
Реализации: PriorityQueue, LinkedList как очередь. Применение: обработка задач, хранение заявок
Интерфейс Queue<E> имеет несколько реализаций в JCF, каждая оптимизирована под разные сценарии. Сегодня фокус на PriorityQueue и LinkedList (как Queue). Эти реализации демонстрируют разнообразие: от строгого FIFO до приоритетной обработки.
LinkedList<E> как Queue
LinkedList — это двусвязный список (doubly-linked list), который реализует Queue<E> (а также List<E> и Deque<E>). Как очередь, она идеальна для FIFO: добавление в конец, извлечение из начала.
Особенности:
FIFO: Строго соблюдается порядок добавления.
Уникальность: Нет, дубликаты разрешены.
Null: Разрешен (LinkedList позволяет null элементы).
Big O: O(1) для offer (добавление в конец), poll (извлечение из начала), peek (просмотр начала). Contains — O(n), так как перебор списка.
Внутренняя работа: Каждый элемент — узел (node) с ссылками на prev и next. Добавление — создание узла и обновление ссылок. Извлечение — удаление первого узла и сдвиг ссылок.
Нюансы:
Эффективна для частых вставок/удалений в концах (O(1)), но медленная для середины (O(n)).
Память: Выше, чем у ArrayDeque (из-за ссылок на prev/next).
Thread-safety: Нет — для многопоточности используйте BlockingQueue.
Дополнительно: Как Deque, поддерживает добавление/извлечение с обоих концов (об этом в следующем уроке).
Когда использовать: Для простых FIFO-очередей с небольшим размером, или когда нужна универсальность (Queue + List).
Пример кода для LinkedList как Queue:
PriorityQueue<E>
PriorityQueue — это приоритетная очередь на основе кучи (binary heap, min-heap по умолчанию). Она не следует FIFO, а извлекает элементы по приоритету (минимальный первый для натуральных типов).
Особенности:
FIFO: Нет — приоритетный порядок (по Comparable или Comparator).
Уникальность: Нет, дубликаты разрешены.
Null: Не разрешен (NullPointerException).
Big O: O(log n) для offer (вставка в кучу), poll (извлечение минимума с перестройкой), peek — O(1). Contains — O(n), так как перебор.
Внутренняя работа: Хранит элементы в массиве как бинарную кучу. При добавлении/извлечении перестраивает кучу (heapify) для поддержания свойства: родитель <= дети. Приоритет определяется compareTo() или Comparator.
Нюансы:
Порядок итерации: Не гарантирован (куча не sorted list).
Comparator: Передайте при создании: new PriorityQueue<>((a, b) -> b - a) для max-heap.
Размер: Resizable, initial capacity 11.
Thread-safety: Нет — используйте PriorityBlockingQueue для потоков.
Custom объекты: Должны реализовывать Comparable<E> или предоставить Comparator, иначе ClassCastException.
Когда использовать: Для задач с приоритетами (например, планировщик задач, Dijkstra алгоритм).
#Java #для_новичков #beginner #Collections #PriorityQueue #LinkedList
Глава 4. Queue и Deque
Реализации: PriorityQueue, LinkedList как очередь. Применение: обработка задач, хранение заявок
Интерфейс Queue<E> имеет несколько реализаций в JCF, каждая оптимизирована под разные сценарии. Сегодня фокус на PriorityQueue и LinkedList (как Queue). Эти реализации демонстрируют разнообразие: от строгого FIFO до приоритетной обработки.
LinkedList<E> как Queue
LinkedList — это двусвязный список (doubly-linked list), который реализует Queue<E> (а также List<E> и Deque<E>). Как очередь, она идеальна для FIFO: добавление в конец, извлечение из начала.
Особенности:
FIFO: Строго соблюдается порядок добавления.
Уникальность: Нет, дубликаты разрешены.
Null: Разрешен (LinkedList позволяет null элементы).
Big O: O(1) для offer (добавление в конец), poll (извлечение из начала), peek (просмотр начала). Contains — O(n), так как перебор списка.
Внутренняя работа: Каждый элемент — узел (node) с ссылками на prev и next. Добавление — создание узла и обновление ссылок. Извлечение — удаление первого узла и сдвиг ссылок.
Нюансы:
Эффективна для частых вставок/удалений в концах (O(1)), но медленная для середины (O(n)).
Память: Выше, чем у ArrayDeque (из-за ссылок на prev/next).
Thread-safety: Нет — для многопоточности используйте BlockingQueue.
Дополнительно: Как Deque, поддерживает добавление/извлечение с обоих концов (об этом в следующем уроке).
Когда использовать: Для простых FIFO-очередей с небольшим размером, или когда нужна универсальность (Queue + List).
Пример кода для LinkedList как Queue:
javaimport java.util.LinkedList;
import java.util.Queue;
public class Main {
public static void main(String[] args) {
Queue<String> queue = new LinkedList<>();
queue.offer("Задача 1"); // Добавление в конец
queue.offer("Задача 2");
queue.offer("Задача 3");
System.out.println(queue); // [Задача 1, Задача 2, Задача 3] — FIFO порядок
System.out.println(queue.peek()); // Задача 1 (просмотр)
System.out.println(queue.poll()); // Задача 1 (извлечение)
System.out.println(queue); // [Задача 2, Задача 3]
queue.offer(null); // Разрешен null
System.out.println(queue.poll()); // Задача 2
}
}
Вывод: Показывает FIFO — элементы извлекаются в порядке добавления, null разрешен.
PriorityQueue<E>
PriorityQueue — это приоритетная очередь на основе кучи (binary heap, min-heap по умолчанию). Она не следует FIFO, а извлекает элементы по приоритету (минимальный первый для натуральных типов).
Особенности:
FIFO: Нет — приоритетный порядок (по Comparable или Comparator).
Уникальность: Нет, дубликаты разрешены.
Null: Не разрешен (NullPointerException).
Big O: O(log n) для offer (вставка в кучу), poll (извлечение минимума с перестройкой), peek — O(1). Contains — O(n), так как перебор.
Внутренняя работа: Хранит элементы в массиве как бинарную кучу. При добавлении/извлечении перестраивает кучу (heapify) для поддержания свойства: родитель <= дети. Приоритет определяется compareTo() или Comparator.
Нюансы:
Порядок итерации: Не гарантирован (куча не sorted list).
Comparator: Передайте при создании: new PriorityQueue<>((a, b) -> b - a) для max-heap.
Размер: Resizable, initial capacity 11.
Thread-safety: Нет — используйте PriorityBlockingQueue для потоков.
Custom объекты: Должны реализовывать Comparable<E> или предоставить Comparator, иначе ClassCastException.
Когда использовать: Для задач с приоритетами (например, планировщик задач, Dijkstra алгоритм).
#Java #для_новичков #beginner #Collections #PriorityQueue #LinkedList
👍2
  Пример кода для PriorityQueue:
Применение очередей: Обработка задач, хранение заявок
Очереди идеальны для сценариев последовательной обработки.
Обработка задач (Task Processing):
Пример: Планировщик задач, где задачи добавляются в очередь и обрабатываются по порядку (FIFO с LinkedList) или по приоритету (PriorityQueue).
Нюанс: В многопоточных системах (например, Thread pool) используйте BlockingQueue для безопасного poll.
Пример кода (простой обработчик):
Хранение заявок (Request Storage):
Пример: Сервер хранит входящие заявки в очередь для последовательной обработки (например, HTTP requests).
С PriorityQueue: Заявки по срочности (high-priority first).
Нюанс: Для реальных систем используйте BlockingQueue (offer/poll с блокировкой при пустой/полной).
Пример кода (приоритетные заявки):
Полезные советы для новичков
LinkedList для простоты: Универсальна для FIFO, легко добавить Deque-функции.
PriorityQueue для приоритетов: Передавайте Comparator для custom порядка (например, max-heap).
Custom классы: Реализуйте Comparable для PriorityQueue, или используйте Comparator.
Пустая очередь: Проверяйте isEmpty() перед poll, или используйте null от poll.
Итерация: For-each для просмотра, но не модифицируйте.
#Java #для_новичков #beginner #Collections #PriorityQueue #LinkedList
javaimport java.util.PriorityQueue;
import java.util.Queue;
public class Main {
public static void main(String[] args) {
Queue<Integer> queue = new PriorityQueue<>();
queue.offer(3);
queue.offer(1);
queue.offer(2);
System.out.println(queue); // [1, 3, 2] — min в начале, но итерация не sorted
System.out.println(queue.peek()); // 1 (минимальный)
System.out.println(queue.poll()); // 1
System.out.println(queue); // [2, 3]
// Max-heap с Comparator
Queue<Integer> maxQueue = new PriorityQueue<>((a, b) -> b - a);
maxQueue.offer(3);
maxQueue.offer(1);
maxQueue.offer(2);
System.out.println(maxQueue.poll()); // 3 (максимальный)
// queue.offer(null); // NPE
}
}
Вывод: Элементы извлекаются по приоритету, не по порядку добавления.
Применение очередей: Обработка задач, хранение заявок
Очереди идеальны для сценариев последовательной обработки.
Обработка задач (Task Processing):
Пример: Планировщик задач, где задачи добавляются в очередь и обрабатываются по порядку (FIFO с LinkedList) или по приоритету (PriorityQueue).
Нюанс: В многопоточных системах (например, Thread pool) используйте BlockingQueue для безопасного poll.
Пример кода (простой обработчик):
javaimport java.util.LinkedList;
import java.util.Queue;
public class TaskProcessor {
private Queue<String> tasks = new LinkedList<>();
public void addTask(String task) {
tasks.offer(task);
}
public void processTasks() {
while (!tasks.isEmpty()) {
String task = tasks.poll();
System.out.println("Обработка: " + task);
}
}
}
public class Main {
public static void main(String[] args) {
TaskProcessor processor = new TaskProcessor();
processor.addTask("Задача 1");
processor.addTask("Задача 2");
processor.processTasks(); // Обработка: Задача 1\nОбработка: Задача 2
}
}
Вывод: Задачи обрабатываются FIFO.
Хранение заявок (Request Storage):
Пример: Сервер хранит входящие заявки в очередь для последовательной обработки (например, HTTP requests).
С PriorityQueue: Заявки по срочности (high-priority first).
Нюанс: Для реальных систем используйте BlockingQueue (offer/poll с блокировкой при пустой/полной).
Пример кода (приоритетные заявки):
javaimport java.util.PriorityQueue;
import java.util.Queue;
class Request implements Comparable<Request> {
private String name;
private int priority; // 1 - высокий, 10 - низкий
public Request(String name, int priority) {
this.name = name;
this.priority = priority;
}
@Override
public int compareTo(Request other) {
return Integer.compare(this.priority, other.priority); // Min-heap по приоритету
}
public String getName() {
return name;
}
}
public class Main {
public static void main(String[] args) {
Queue<Request> requests = new PriorityQueue<>();
requests.offer(new Request("Заявка A", 5));
requests.offer(new Request("Заявка B", 1)); // Высокий приоритет
requests.offer(new Request("Заявка C", 3));
while (!requests.isEmpty()) {
System.out.println("Обработка: " + requests.poll().getName()); // Заявка B, Заявка C, Заявка A
}
}
}
Вывод: Заявки обрабатываются по приоритету.
Полезные советы для новичков
LinkedList для простоты: Универсальна для FIFO, легко добавить Deque-функции.
PriorityQueue для приоритетов: Передавайте Comparator для custom порядка (например, max-heap).
Custom классы: Реализуйте Comparable для PriorityQueue, или используйте Comparator.
Пустая очередь: Проверяйте isEmpty() перед poll, или используйте null от poll.
Итерация: For-each для просмотра, но не модифицируйте.
#Java #для_новичков #beginner #Collections #PriorityQueue #LinkedList
👍1
  Реактивное программирование
R2DBC vs JDBC: реактивные базы данных
Исторический контекст: что такое JDBC и почему он доминировал десятилетиями
JDBC — это стандартный API Java для доступа к реляционным базам данных, появившийся ещё в JDK 1.1 (1997 год).
Он позволяет выполнять SQL-запросы, управлять соединениями и обрабатывать результаты через унифицированный интерфейс, независимо от конкретной БД (PostgreSQL, MySQL, Oracle и т.д.).
Ключевые компоненты JDBC:
DriverManager или DataSource: Для получения соединения (Connection).
Statement/PreparedStatement: Для выполнения SQL (executeQuery, executeUpdate).
ResultSet: Для чтения результатов (next(), getString() и т.д.).
Transaction management: commit(), rollback().
Пример простого JDBC-кода:
Это синхронно и блокирующе: executeQuery() "виснет" до ответа от БД, блокируя текущий поток.
В традиционных приложениях (как Spring MVC) это работало: каждый запрос — отдельный поток из пула (например, Tomcat с 200 потоками), и если БД отвечает быстро, проблем нет. Но под высокой нагрузкой или с медленными запросами (сетевые задержки, сложные джойны) пул исчерпывается: потоки "спят" в ожидании IO, CPU простаивает, а новые запросы ждут в очереди, вызывая таймауты и отказы. Это классическая проблема асинхронщины: JDBC не предназначен для non-blocking IO, он полагается на blocking calls операционной системы.
В реактивных приложениях (WebFlux) использование JDBC — антипаттерн: если контроллер возвращает Mono, но внутри — blocking JDBC, весь выигрыш теряется. Поток из event-loop (Netty) блокируется, снижая throughput (пропускную способность). Вот почему нужен новый подход.
Проблемы JDBC в реактивном контексте: почему старый стандарт не справляется
Давайте разберём проблемы JDBC подробно, чтобы понять мотивацию R2DBC:
Блокирующая природа: Все операции (connect, query, fetch) — синхронны. В асинхронном коде это требует обёрток вроде CompletableFuture или offload на отдельный пул (Schedulers.boundedElastic()), но это хак: теряется истинная реактивность, и под нагрузкой пулы переполняются.
Отсутствие backpressure: ResultSet — pull-модель (next() получает данные), но без контроля темпа. Если результат огромный (миллионы строк), буфер переполняется, рискуя OOM (OutOfMemoryError). В реактивном мире (push с backpressure) это несовместимо.
Управление соединениями: JDBC полагается на пулы (HikariCP), но они ориентированы на blocking: соединение "занято" весь запрос. В реактиве нужно multiplexing — одно соединение для многих операций асинхронно.
Транзакции: @Transactional в Spring работает, но в реактиве требует специальной поддержки (reactive transactions), иначе — блокировки.
Масштабируемость: Под 10k+ RPS (requests per second) с БД-запросами JDBC требует огромных пулов потоков (тысячи), что жрёт память (каждый поток ~1MB стека) и контекст-свичинг.
Интеграция с Reactor: Нет native Publisher — результаты не "текут" как Flux, требуя ручной конвертации, что добавляет boilerplate и риски.
В итоге, JDBC — отличный для legacy или низконагруженных приложений, но в микросервисах с WebFlux он "ломает" реактивный стек, возвращая к болям callback-ада и ожиданий.
Введение в R2DBC: реактивный стандарт для реляционных БД
R2DBC — это спецификация (с 2019 года, под эгидой Spring и Pivotal), определяющая API для доступа к реляционным БД в реактивном стиле. Это не замена JDBC, а параллельный стандарт, ориентированный на non-blocking IO.
#Java #middle #Reactor #WebFlux #Mono #Flux #R2DBC
R2DBC vs JDBC: реактивные базы данных
Исторический контекст: что такое JDBC и почему он доминировал десятилетиями
JDBC — это стандартный API Java для доступа к реляционным базам данных, появившийся ещё в JDK 1.1 (1997 год).
Он позволяет выполнять SQL-запросы, управлять соединениями и обрабатывать результаты через унифицированный интерфейс, независимо от конкретной БД (PostgreSQL, MySQL, Oracle и т.д.).
Ключевые компоненты JDBC:
DriverManager или DataSource: Для получения соединения (Connection).
Statement/PreparedStatement: Для выполнения SQL (executeQuery, executeUpdate).
ResultSet: Для чтения результатов (next(), getString() и т.д.).
Transaction management: commit(), rollback().
Пример простого JDBC-кода:
import java.sql.*;
public class JdbcExample {
public static void main(String[] args) {
try (Connection conn = DriverManager.getConnection("jdbc:postgresql://localhost:5432/db", "user", "pass");
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE id = ?")) {
stmt.setLong(1, 1L);
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
System.out.println("User: " + rs.getString("name"));
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
Это синхронно и блокирующе: executeQuery() "виснет" до ответа от БД, блокируя текущий поток.
В традиционных приложениях (как Spring MVC) это работало: каждый запрос — отдельный поток из пула (например, Tomcat с 200 потоками), и если БД отвечает быстро, проблем нет. Но под высокой нагрузкой или с медленными запросами (сетевые задержки, сложные джойны) пул исчерпывается: потоки "спят" в ожидании IO, CPU простаивает, а новые запросы ждут в очереди, вызывая таймауты и отказы. Это классическая проблема асинхронщины: JDBC не предназначен для non-blocking IO, он полагается на blocking calls операционной системы.
В реактивных приложениях (WebFlux) использование JDBC — антипаттерн: если контроллер возвращает Mono, но внутри — blocking JDBC, весь выигрыш теряется. Поток из event-loop (Netty) блокируется, снижая throughput (пропускную способность). Вот почему нужен новый подход.
Проблемы JDBC в реактивном контексте: почему старый стандарт не справляется
Давайте разберём проблемы JDBC подробно, чтобы понять мотивацию R2DBC:
Блокирующая природа: Все операции (connect, query, fetch) — синхронны. В асинхронном коде это требует обёрток вроде CompletableFuture или offload на отдельный пул (Schedulers.boundedElastic()), но это хак: теряется истинная реактивность, и под нагрузкой пулы переполняются.
Отсутствие backpressure: ResultSet — pull-модель (next() получает данные), но без контроля темпа. Если результат огромный (миллионы строк), буфер переполняется, рискуя OOM (OutOfMemoryError). В реактивном мире (push с backpressure) это несовместимо.
Управление соединениями: JDBC полагается на пулы (HikariCP), но они ориентированы на blocking: соединение "занято" весь запрос. В реактиве нужно multiplexing — одно соединение для многих операций асинхронно.
Транзакции: @Transactional в Spring работает, но в реактиве требует специальной поддержки (reactive transactions), иначе — блокировки.
Масштабируемость: Под 10k+ RPS (requests per second) с БД-запросами JDBC требует огромных пулов потоков (тысячи), что жрёт память (каждый поток ~1MB стека) и контекст-свичинг.
Интеграция с Reactor: Нет native Publisher — результаты не "текут" как Flux, требуя ручной конвертации, что добавляет boilerplate и риски.
В итоге, JDBC — отличный для legacy или низконагруженных приложений, но в микросервисах с WebFlux он "ломает" реактивный стек, возвращая к болям callback-ада и ожиданий.
Введение в R2DBC: реактивный стандарт для реляционных БД
R2DBC — это спецификация (с 2019 года, под эгидой Spring и Pivotal), определяющая API для доступа к реляционным БД в реактивном стиле. Это не замена JDBC, а параллельный стандарт, ориентированный на non-blocking IO.
#Java #middle #Reactor #WebFlux #Mono #Flux #R2DBC
👍2
  Ключевые идеи:
Publisher-based API: Все операции возвращают Publisher (Mono/Flux из Reactive Streams): Connection как Mono<Connection>, Statement.execute() как Flux<Row>.
Non-blocking от начала до конца: Использует асинхронные драйверы (для PostgreSQL, MySQL и т.д.), где соединения мультиплексируются — одно для многих запросов.
Backpressure встроено: Результаты (Flux<Row>) уважают request(n): если подписчик не готов, БД не шлёт данные, избегая перегрузки.
Транзакции реактивные: Поддержка @Transactional с Mono/Flux.
Интеграция с экосистемой: Spring Data R2DBC — аналог Spring Data JPA, с репозиториями, @Query и CRUD.
Драйверы: r2dbc-postgresql, r2dbc-mysql и т.д. — реализуют спецификацию, используя неблокирующие сокеты (Netty или аналог).
Пример базового R2DBC-кода (без Spring):
Здесь usingWhen — реактивный try-with-resources: создаёт соединение асинхронно, выполняет запрос как Flux<Result>, map извлекает данные. Нет блокировок: если БД медленная, поток свободен.
Spring Data R2DBC: упрощение с репозиториями и аннотациями
Spring Data R2DBC — модуль, который абстрагирует R2DBC, как Spring Data JPA для JDBC.
Добавьте зависимость:
Настройте в application.properties:
Репозитории:
Сущность:
В сервисе/контроллере:
В контроллере:
Это декларативно: repo.findAll() — Flux, который "течёт" из БД без блокировок. Транзакции: @Transactional на методе — reactive, rollback асинхронно.
Расширенный пример: пагинация с ReactiveSortingRepository и Pageable.
Практические советы и подводные камни
Выбор БД: PostgreSQL — лучший для R2DBC (полная поддержка async).
Тестирование: Embedded H2 с r2dbc-h2, ReactiveTest для StepVerifier.
Камень: Нет full ORM (как JPA entities с relations) — используйте ручные joins или Spring Data Projections.
Камень: Транзакции не поддерживают propagation в nested методах fully — будьте осторожны.
Совет: Для hybrid (JDBC + R2DBC) — используйте разные DataSource, но избегайте в одном приложении.
Совет: Мониторьте с Micrometer: метрики на запросы, соединения.
#Java #middle #Reactor #WebFlux #Mono #Flux #R2DBC
Publisher-based API: Все операции возвращают Publisher (Mono/Flux из Reactive Streams): Connection как Mono<Connection>, Statement.execute() как Flux<Row>.
Non-blocking от начала до конца: Использует асинхронные драйверы (для PostgreSQL, MySQL и т.д.), где соединения мультиплексируются — одно для многих запросов.
Backpressure встроено: Результаты (Flux<Row>) уважают request(n): если подписчик не готов, БД не шлёт данные, избегая перегрузки.
Транзакции реактивные: Поддержка @Transactional с Mono/Flux.
Интеграция с экосистемой: Spring Data R2DBC — аналог Spring Data JPA, с репозиториями, @Query и CRUD.
Драйверы: r2dbc-postgresql, r2dbc-mysql и т.д. — реализуют спецификацию, используя неблокирующие сокеты (Netty или аналог).
Пример базового R2DBC-кода (без Spring):
import io.r2dbc.spi.ConnectionFactories;
import io.r2dbc.spi.ConnectionFactory;
import reactor.core.publisher.Flux;
public void createConnectionFactory () {
ConnectionFactory factory = ConnectionFactories.get("r2dbc:postgresql://localhost:5432/db?username=user&password=pass");
Flux<String> namesFlux = Flux.usingWhen(
factory.create(), // Асинхронно создать соединение
conn -> conn.createStatement("SELECT name FROM users").execute().flatMap(result -> result.map((row, metadata) -> row.get("name", String.class))),
conn -> conn.close() // Асинхронно закрыть
);
namesFlux.subscribe(System.out::println); // Строки приходят асинхронно
}
Здесь usingWhen — реактивный try-with-resources: создаёт соединение асинхронно, выполняет запрос как Flux<Result>, map извлекает данные. Нет блокировок: если БД медленная, поток свободен.
Spring Data R2DBC: упрощение с репозиториями и аннотациями
Spring Data R2DBC — модуль, который абстрагирует R2DBC, как Spring Data JPA для JDBC.
Добавьте зависимость:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<dependency>
<groupId>io.r2dbc</groupId>
<artifactId>r2dbc-postgresql</artifactId> <!-- Для PostgreSQL -->
</dependency>
Настройте в application.properties:
spring.r2dbc.url=r2dbc:postgresql://localhost:5432/db
spring.r2dbc.username=user
spring.r2dbc.password=pass
Репозитории:
ReactiveRepository extends ReactiveCrudRepository<Entity, ID>.
Сущность:
@Entity
public class User {
@Id
private Long id;
private String name;
// Getters/setters
}
public interface UserRepository extends ReactiveCrudRepository<User, Long> {
@Query("SELECT * FROM users WHERE name LIKE :name")
Flux<User> findByNameLike(String name);
}
В сервисе/контроллере:
@Service
public class UserService {
private final UserRepository repo;
public UserService(UserRepository repo) {
this.repo = repo;
}
public Flux<User> findAll() {
return repo.findAll(); // Flux асинхронно
}
public Mono<User> save(User user) {
return repo.save(user);
}
}
В контроллере:
@GetMapping("/users")
public Flux<User> getAllUsers() {
     return userService.findAll();
}Это декларативно: repo.findAll() — Flux, который "течёт" из БД без блокировок. Транзакции: @Transactional на методе — reactive, rollback асинхронно.
Расширенный пример: пагинация с ReactiveSortingRepository и Pageable.
public interface UserRepository extends ReactiveSortingRepository<User, Long> {}
Flux<User> paged = repo.findAll(Sort.by("name").ascending()).skip(10).take(20); // Простая пагинация
Для complex: используйте @Query с параметрами, или Criteria API.Практические советы и подводные камни
Выбор БД: PostgreSQL — лучший для R2DBC (полная поддержка async).
Тестирование: Embedded H2 с r2dbc-h2, ReactiveTest для StepVerifier.
Камень: Нет full ORM (как JPA entities с relations) — используйте ручные joins или Spring Data Projections.
Камень: Транзакции не поддерживают propagation в nested методах fully — будьте осторожны.
Совет: Для hybrid (JDBC + R2DBC) — используйте разные DataSource, но избегайте в одном приложении.
Совет: Мониторьте с Micrometer: метрики на запросы, соединения.
#Java #middle #Reactor #WebFlux #Mono #Flux #R2DBC
👍2
  