DatabaseClient у Spring WebFlux
DatabaseClient — це низькорівневий реактивний API для роботи з реляційними базами даних у Spring WebFlux. Він входить до складу Spring Data R2DBC та дозволяє виконувати SQL-запити без використання блокуючого JDBC.
Якщо JdbcTemplate є стандартним рішенням для Spring MVC, то DatabaseClient виконує аналогічну роль у реактивному стеку. Він надає простий спосіб виконання SQL-запитів, прив'язки параметрів та перетворення результатів у Java-об'єкти.
DatabaseClient працює поверх драйвера R2DBC і повертає реактивні типи Mono<T> та Flux<T>, що дозволяє повністю зберігати неблокуючу модель виконання.
DatabaseClient добре підходить у таких випадках:
- необхідно виконувати власні SQL-запити;
- потрібен повний контроль над SQL;
- проєкт використовує Spring WebFlux та R2DBC;
- запити занадто складні для похідних методів Repository;
- необхідно отримувати лише окремі поля, а не цілі сутності;
- потрібно виконувати JOIN, агрегатні функції або складні фільтрації.
Фактично DatabaseClient займає проміжне місце між реактивним Repository та повністю ручною роботою з драйвером R2DBC.
Додавання залежностей
Для Maven необхідно підключити Spring Data R2DBC та драйвер потрібної бази даних.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<dependency>
<groupId>io.r2dbc</groupId>
<artifactId>r2dbc-postgresql</artifactId>
</dependency>
</dependencies>
Налаштування підключення
У більшості випадків достатньо стандартного налаштування в application.yml.
spring:
r2dbc:
url: r2dbc:postgresql://localhost:5432/demo
username: postgres
password: password
Spring Boot автоматично створить ConnectionFactory та DatabaseClient.
Ін'єкція DatabaseClient
У сервісі достатньо отримати DatabaseClient через конструктор.
@Service
public class UserService {
private final DatabaseClient databaseClient;
public UserService(DatabaseClient databaseClient) {
this.databaseClient = databaseClient;
}
}
Отримання одного запису
public Mono<User> findById(Long id) {
return databaseClient
.sql("""
SELECT id, name, email
FROM users
WHERE id = :id
""")
.bind("id", id)
.map((row, metadata) -> new User(
row.get("id", Long.class),
row.get("name", String.class),
row.get("email", String.class)
))
.one();
}
Метод one() повертає Mono<User>. Якщо запис не знайдено, буде повернуто порожній Mono.
Отримання списку записів
public Flux<User> findAll() {
return databaseClient
.sql("""
SELECT id, name, email
FROM users
ORDER BY name
""")
.map((row, metadata) -> new User(
row.get("id", Long.class),
row.get("name", String.class),
row.get("email", String.class)
))
.all();
}
Метод all() повертає Flux<User>.
Передача параметрів
DatabaseClient підтримує іменовані параметри.
databaseClient
.sql("""
SELECT *
FROM users
WHERE age > :age
AND status = :status
""")
.bind("age", 18)
.bind("status", "ACTIVE");
Такий підхід робить SQL більш читабельним і захищає від SQL Injection.
INSERT-запит
public Mono<Integer> save(User user) {
return databaseClient
.sql("""
INSERT INTO users(name, email)
VALUES (:name, :email)
""")
.bind("name", user.getName())
.bind("email", user.getEmail())
.fetch()
.rowsUpdated();
}
Метод rowsUpdated() повертає кількість змінених рядків.
UPDATE-запит
public Mono<Integer> updateEmail(Long id, String email) {
return databaseClient
.sql("""
UPDATE users
SET email = :email
WHERE id = :id
""")
.bind("email", email)
.bind("id", id)
.fetch()
.rowsUpdated();
}
DELETE-запит
public Mono<Integer> delete(Long id) {
return databaseClient
.sql("""
DELETE
FROM users
WHERE id = :id
""")
.bind("id", id)
.fetch()
.rowsUpdated();
}
Переваги DatabaseClient
- повністю реактивний API;
- не блокує потоки;
- працює без Hibernate;
- простий у використанні;
- повний контроль над SQL;
- легко оптимізувати продуктивність запитів;
- добре підходить для складних JOIN та агрегатних запитів;
- менше прихованої логіки порівняно з ORM.
Недоліки
- не підтримує автоматичне формування SQL, як JPA;
- не має кешування першого рівня;
- немає механізму Lazy Loading;
- необхідно самостійно описувати SQL;
- потрібно вручну виконувати мапінг рядків у Java-об'єкти;
- при великій кількості запитів код може стати доволі об'ємним.
DatabaseClient vs R2dbcRepository
Обидва підходи є частиною Spring Data R2DBC, але вирішують різні задачі.
| R2dbcRepository | DatabaseClient |
|---|---|
| Автоматична реалізація CRUD | Ручне написання SQL |
| Менше коду | Більше контролю |
| Прості CRUD-операції | Складні SQL-запити |
| Менше гнучкості | Максимальна гнучкість |
| Похідні методи пошуку | Будь-який SQL |
| Менше можливостей оптимізації | Легше оптимізувати продуктивність |
У невеликих CRUD-проєктах зазвичай достатньо R2dbcRepository. Він дозволяє швидко реалізувати базові операції без написання SQL.
Якщо ж застосунок активно працює з великими обсягами даних, використовує складні JOIN, агрегації, CTE, віконні функції або потребує максимальної продуктивності, DatabaseClient стає значно кращим вибором. Він надає повний контроль над SQL і дозволяє використовувати всі можливості конкретної СУБД.
На практиці ці підходи часто поєднують: для звичайних CRUD-операцій використовують R2dbcRepository, а для складних або оптимізованих запитів — DatabaseClient. Це дозволяє отримати баланс між швидкістю розробки та продуктивністю застосунку.
Коментарі
Дописати коментар