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

© Habrahabr.ru