TrueSql – заново учимся ходить в базу данных. Часть 1 – пять Fetch’ей

Введение
Приветствую! Недавно к нам обратился ветеран кровавых энтерпрайз-войн, который много лет ездил на паровозике Spring Data. Однако, три месяца назад гуки заложили в спринт оптимизацию производительности CRUD-микросервиса. Колеса крутятся, дым валит и паровозик уже весь похож на вулкан, но таски в Джире не двигаются. Было принято решение оставить паровоз и переписать микросервис на TrueSql. Вскрылась одна проблема — команда разработчиков не умеет ходить в БД прямо, только по-диагонали и кругом. Мы открываем серию статей, где будем заново учиться ходить в БД в ритме Ча-Ча-Ча.
None-Uno-UnoZero-List-Stream
Нашей целью было сделать поход в базу данных максимально простым и интуитивным. В API TrueSql всего 5 методов для интерпретации ResultSet, приходящего из БД в ответ на запрос. В серии этих статей вы убедитесь, что DSL TrueSql доведен до идеала. Условимся о нотации:
Scalar
— Java-класс под который есть type-binding.DTO
— структура данных, состоящая из скаляров или списков, хранящих скаляры или другиеDTO
.
fetchNone () → null
Не возвращает структур данных. Используйте его, когда вам не нужны выходные строки, например удалим клиента:
ds.q("delete from users where id = ?", 42L).fetchNone();
Напомним, что ds
— экземпляр класса-наследника DataSourceW (статья о конфигурации). Метод .q(...)
принимает SQL-запрос и аргументы через запятую. fetchNone () может пригодиться при работе с командами Insert, Update, Delete.
fetchOne () → Dto | Scalar
Ожидает в ответе ровно одну структуру данных, иначе будет исключение TooFewRowsException или TooMuchRowsException. Например: выбираем имя клиента
var name = ds.q("select name from users where id = ?", 2L)
.fetchOne(String.class);
Заметим, в данном случае структуру данных для маппинга нужно указать аргументом функции .fetchOne (String.class).
Можно использовать и классы «примитивов», если вы уверены, что соответствующая колонка не может заnullиться. Например, нам нужен id города в котором находится клиника:
var cityId = ds.q("select city_id from clinic where id = ?", 1L)
.fetchOne(int.class);
Выберем клиентов и замапим в DTO User:
record User(long id, String name, String info) { }
var user = cn.q("select id, name, info from users where id = ?", 1L)
.fetchOne(User.class);
Мы рекомендуем не писать DTO руками, просто добавьте .g
:
var user = ds.q("select id, name, info from users where id = ?", 1L)
.g.fetchOne(User.class);
fetchOneOrZero () → Dto | Scalar | null
Ожидает из ответа одну или ноль структур данных. Если БД не прислала строк в ответ на запрос, результат будет иметь значение null.
var amount = ds.q("""
select
sum(b.amount) as amount
from bill b
join user_bills ub on b.id = ub.bill_id
where user_id = ?""", 3L
).fetchOneOrZero(BigDecimal.class);
Здесь мы пользуемся fetchOneOrZero () потому что не уверены, что клиент платил нам деньги.
fetchList () → List
Собирает список структур данных из ответа. Рассмотрим пример с маппингом более сложных DTO. В примере ниже мы получаем список структур данных с городом и клиниками, которые в нем находятся.
record CityClinics(String city, List clinics) {}
var citiesClinics = ds.q("""
select
ci.name,
cl.name
from city ci
join clinic cl on ci.id = cl.city_id"""
).fetchList(CityClinics.class);
А здесь мы выбрали клиентов с их чеками. Фильтровали по id клиентов.
record Bill(long id, BigDecimal amount, LocalDate date) {}
record User(long id, String name, UserSex sex, List bills) {}
var userIds = List.of(1L,3L,5L);
var usersBills = cn.q("""
select
u.id
u.name,
u.enum_user_sex,
b.id,
b.amount,
b.date
from user u
left join user_bills ub on u.id = ub.user_id
left join bill b on ub.bill_id = b.id
where u.id in (?)""", unfold(userIds)
).fetchList(User.class);
Невероятно крутой и мощный функционал! Однако, сейчас мы не будем развивать тему Tree-выборок, unfold-параметров и (внезапно!) бинда enum’ов — для этого будет отдельная статья ;)
fetchStream () → Stream
Возвращает Stream. Нужен если вы получаете большое количество строк в ответе и можете обрабатывать каждую запись отдельно:
try (
var users = ds.q("select id, name from users")
.g.fetchStream(User.class)
) {
for (var user : users) {
// ...
}
}

Ни одна библиотека не позволяет ходить в БД так круто, как это делает TrueSql
Таким образом, мы начали обучать отряд разработчиков хождению в БД. Им это крайне понравилось так как теперь паровозик не мешает им ходить в БД прямо!
Сайт проекта: https://truej.net/
Документация. Всем, кто уже настрадался с паровозиком Spring Data и его друзьями, предлагаю поставить звездочку. Может это спасет вас от кривой Фреди-Крюгера.
В следующих сериях:
Сгенерированные колонки и количество обновленных строк
Транзакции и работа с соединениями
Хранимые процедуры
unfold-параметры
Батчинг
Композиция TrueSql DSL
Ча-Ча-Ча с базой данных — advanced fetching