Руководство по Jdbi

Руководство по Jdbi

1. Вступление

В этой статье мы рассмотрим, как запросить реляционную базу данных с помощьюjdbi.

Jdbi - это библиотека Java с открытым исходным кодом (лицензия Apache), которая используетlambda expressions иreflection для обеспечения более дружественного интерфейса более высокого уровня, чемJDBC для доступа к базе данных.

Jdbi, however, isn’t an ORM;, несмотря на то, что у него есть дополнительный модуль сопоставления объектов SQL, у него нет сеанса с присоединенными объектами, уровня независимости базы данных и каких-либо других наворотов типичного ORM.

2. Настройка Jdbi

Jdbi состоит из ядра и нескольких дополнительных модулей.

Для начала нам просто нужно включить модуль ядра в наши зависимости:


    
        org.jdbi
        jdbi3-core
        3.1.0
    

В этой статье мы покажем примеры использования базы данных HSQL:


    org.hsqldb
    hsqldb
    2.4.0
    test

Мы можем найти последнюю версиюjdbi3-core,HSQLDB и других модулей Jdbi на Maven Central.

3. Подключение к базе данных

Сначала нам нужно подключиться к базе данных. Для этого нам нужно указать параметры подключения.

Отправной точкой является классJdbi:

Jdbi jdbi = Jdbi.create("jdbc:hsqldb:mem:testDB", "sa", "");

Здесь мы указываем URL-адрес подключения, имя пользователя и, конечно же, пароль.

3.1. Дополнительные параметры

Если нам нужно предоставить другие параметры, мы используем перегруженный метод, принимающий объектProperties:

Properties properties = new Properties();
properties.setProperty("username", "sa");
properties.setProperty("password", "");
Jdbi jdbi = Jdbi.create("jdbc:hsqldb:mem:testDB", properties);

В этих примерах мы сохранили экземплярJdbi в локальной переменной. Это потому, что мы будем использовать его для отправки операторов и запросов в базу данных.

Фактически, простой вызовcreate не устанавливает никакого соединения с БД. Он просто сохраняет параметры подключения на потом.

3.2. ИспользуяDataSource

Если мы подключаемся к базе данных с помощьюDataSource, как это обычно бывает, мы можем использовать соответствующую перегрузкуcreate:

Jdbi jdbi = Jdbi.create(datasource);

3.3. Работа с ручками

Фактические подключения к базе данных представлены экземплярами классаHandle.

Самый простой способ работать с ручками и автоматически закрывать их с помощью лямбда-выражений:

jdbi.useHandle(handle -> {
    doStuffWith(handle);
});

Мы вызываемuseHandle, когда нам не нужно возвращать значение.

В противном случае мы используемwithHandle:

jdbi.withHandle(handle -> {
    return computeValue(handle);
});

Также возможно, хотя и не рекомендуется, вручную открыть дескриптор соединения; в этом случае мы должны закрыть его, когда закончим:

Jdbi jdbi = Jdbi.create("jdbc:hsqldb:mem:testDB", "sa", "");
try (Handle handle = jdbi.open()) {
    doStuffWith(handle);
}

К счастью, как мы видим,Handle реализуетCloseable, поэтому его можно использовать сtry-with-resources.

4. Простые заявления

Теперь, когда мы знаем, как установить соединение, давайте посмотрим, как его использовать.

В этом разделе мы создадим простую таблицу, которую будем использовать на протяжении всей статьи.

Чтобы отправить такие операторы, какcreate table, в базу данных, мы используем методexecute:

handle.execute(
  "create table project "
  + "(id integer identity, name varchar(50), url varchar(100))");

execute возвращает количество строк, на которые повлиял оператор:

int updateCount = handle.execute(
  "insert into project values "
  + "(1, 'tutorials', 'github.com/eugenp/tutorials')");

assertEquals(1, updateCount);

На самом деле, execute - это просто удобный метод.

Мы рассмотрим более сложные варианты использования в следующих разделах, но перед этим нам нужно научиться извлекать результаты из базы данных.

5. Запросы к базе данных

Самым простым выражением, которое дает результаты из БД, является запрос SQL.

Чтобы выдать запрос с помощью Jdbi Handle, нам нужно, как минимум:

  1. создать запрос

  2. выбрать способ представления каждой строки

  3. перебрать результаты

Теперь мы рассмотрим каждый из пунктов выше.

5.1. Создание запроса

Неудивительно, чтоJdbi represents queries as instances of the Query class.

Мы можем получить один из ручки:

Query query = handle.createQuery("select * from project");

5.2. Отображение результатов

Jdbi абстрагируется от JDBCResultSet, который имеет довольно громоздкий API.

Следовательно, он предлагает несколько возможностей доступа к столбцам, полученным в результате запроса или некоторого другого оператора, который возвращает результат. Теперь мы рассмотрим самые простые.

Мы можем представить каждую строку в виде карты:

query.mapToMap();

Ключами карты будут названия выбранных столбцов.

Или, когда запрос возвращает один столбец, мы можем сопоставить его с желаемым типом Java:

handle.createQuery("select name from project").mapTo(String.class);

Jdbi has built-in mappers for many common classes. Те, которые относятся к какой-либо библиотеке или системе баз данных, предоставляются в отдельных модулях.

Конечно, мы также можем определить и зарегистрировать наших картографов. Мы поговорим об этом в следующем разделе.

Наконец, мы можем отобразить строки в bean-компонент или в другой пользовательский класс. Опять же, мы увидим более сложные параметры в специальном разделе.

5.3. Итерация результатов

Как только мы решили, как сопоставить результаты, вызвав соответствующий методwe receive a ResultIterable object.

Затем мы можем использовать его для перебора результатов, по одной строке за раз.

Здесь мы рассмотрим наиболее распространенные варианты.

Мы можем просто накапливать результаты в списке:

List> results = query.mapToMap().list();

Или к другому типуCollection:

List results = query.mapTo(String.class).collect(Collectors.toSet());

Или мы можем перебрать результаты в виде потока:

query.mapTo(String.class).useStream((Stream stream) -> {
    doStuffWith(stream)
});

Здесь мы явно ввели переменнуюstream для ясности, но в этом нет необходимости.

5.4. Получение единственного результата

В качестве особого случая, когда мы ожидаем или заинтересованы только в одной строке, у нас есть несколько доступных методов.

Если мы хотимat most one result, мы можем использоватьfindFirst:

Optional> first = query.mapToMap().findFirst();

Как мы видим, он возвращает значениеOptional, которое присутствует только в том случае, если запрос возвращает хотя бы один результат.

Если запрос возвращает более одной строки, возвращается только первая.

Если вместо этого мы хотимone and only one result, мы используемfindOnly:

Date onlyResult = query.mapTo(Date.class).findOnly();

Наконец, если результатов нет или больше одного,findOnly выдаетIllegalStateException.

6. Параметры привязки

Частоqueries have a fixed portion and a parameterized portion. имеет несколько преимуществ, в том числе:

  • безопасность: избегая конкатенации строк, мы предотвращаем внедрение SQL

  • простота: нам не нужно помнить точный синтаксис сложных типов данных, таких как временные метки

  • производительность: статическая часть запроса может быть проанализирована один раз и кэширована

Jdbi поддерживает как позиционные, так и именованные параметры.

Мы вставляем позиционные параметры в виде вопросительных знаков в запросе или утверждении:

Query positionalParamsQuery =
  handle.createQuery("select * from project where name = ?");

Именованные параметры вместо этого начинаются с двоеточия:

Query namedParamsQuery =
  handle.createQuery("select * from project where url like :pattern");

В любом случае, чтобы задать значение параметра, мы используем один из вариантов методаbind:

positionalParamsQuery.bind(0, "tutorials");
namedParamsQuery.bind("pattern", "%github.com/eugenp/%");

Обратите внимание, что в отличие от JDBC, индексы начинаются с 0.

6.1. Привязка нескольких именованных параметров одновременно

Мы также можем связать несколько именованных параметров вместе, используя объект.

Допустим, у нас есть такой простой запрос:

Query query = handle.createQuery(
  "select id from project where name = :name and url = :url");
Map params = new HashMap<>();
params.put("name", "REST with Spring");
params.put("url", "github.com/eugenp/REST-With-Spring");

Тогда, например, мы можем использовать карту:

query.bindMap(params);

Или мы можем использовать объект различными способами. Здесь, например, мы связываем объект, который следует соглашению JavaBean:

query.bindBean(paramsBean);

Но мы также можем связать поля или методы объекта; для всех поддерживаемых опций см.the Jdbi documentation.

7. Выдача более сложных отчетов

Теперь, когда мы увидели запросы, значения и параметры, мы можем вернуться к утверждениям и применить те же знания.

Напомним, что методexecute, который мы видели ранее, - это просто удобный ярлык.

Фактически, как и в запросах,DDL and DML statements are represented as instances of the class Update.

Мы можем получить его, вызвав методcreateUpdate для дескриптора:

Update update = handle.createUpdate(
  "INSERT INTO PROJECT (NAME, URL) VALUES (:name, :url)");

Затем вUpdate у нас есть все методы привязки, которые есть вQuery, поэтому раздел 6. относится и к обновлениям .url

Операторы выполняются, когда мы вызываем, сюрприз,execute:

int rows = update.execute();

Как мы уже видели, он возвращает количество затронутых строк.

7.1. Извлечение значений столбца с автоинкрементом

В особом случае, когда у нас есть оператор вставки с автоматически сгенерированными столбцами (обычно с автоинкрементом или последовательностями), мы можем захотеть получить сгенерированные значения.

Тогда мы вызываем неexecute, аexecuteAndReturnGeneratedKeys:

Update update = handle.createUpdate(
  "INSERT INTO PROJECT (NAME, URL) "
  + "VALUES ('tutorials', 'github.com/eugenp/tutorials')");
ResultBearing generatedKeys = update.executeAndReturnGeneratedKeys();

ResultBearing is the same interface implemented by the Query class, который мы видели ранее, поэтому мы уже знаем, как его использовать:

generatedKeys.mapToMap()
  .findOnly().get("id");

8. операции

Нам нужна транзакция, когда нам нужно выполнить несколько операторов как одну атомарную операцию.

Как и в случае с дескрипторами соединения, мы вводим транзакцию, вызывая метод с замыканием:

handle.useTransaction((Handle h) -> {
    haveFunWith(h);
});

И, как и с дескрипторами, транзакция автоматически закрывается, когда закрытие возвращается.

However, we must commit or rollback the transaction перед возвратом:

handle.useTransaction((Handle h) -> {
    h.execute("...");
    h.commit();
});

Однако, если исключение выдается из закрытия, Jdbi автоматически откатывает транзакцию.

Как и в случае с дескрипторами, у нас есть специальный методinTransaction, если мы хотим вернуть что-то из закрытия:

handle.inTransaction((Handle h) -> {
    h.execute("...");
    h.commit();
    return true;
});

8.1. Ручное управление транзакциями

Хотя в общем случае это не рекомендуется, мы также можем выполнить транзакциюbegin иclose вручную:

handle.begin();
// ...
handle.commit();
handle.close();

9. Выводы и дальнейшее чтение

В этом руководстве мы представили ядро ​​Jdbi:queries, statements, and transactions.

Мы упустили некоторые расширенные функции, такие как настраиваемое сопоставление строк и столбцов и пакетная обработка.

Мы также не обсуждали ни один из дополнительных модулей, в первую очередь расширение объекта SQL.

Все подробно представлено вJdbi documentation.

Реализацию всех этих примеров и фрагментов кода можно найти вthe GitHub project - это проект Maven, поэтому его легко импортировать и запускать как есть.