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 | 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


ссылка на оригинал статьи https://habr.com/ru/articles/885414/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *