
Введение
Приветствую! Недавно к нам обратился ветеран кровавых энтерпрайз-войн, который много лет ездил на паровозике 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 | Scalar>
Собирает список структур данных из ответа. Рассмотрим пример с маппингом более сложных DTO. В примере ниже мы получаем список структур данных с городом и клиниками, которые в нем находятся.
record CityClinics(String city, List<String> 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<Bill> 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<Dto | Scalar>
Возвращает 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
