Руководство по 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, нам нужно, как минимум:
-
создать запрос
-
выбрать способ представления каждой строки
-
перебрать результаты
Теперь мы рассмотрим каждый из пунктов выше.
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
Или к другому типу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
Как мы видим, он возвращает значение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, поэтому его легко импортировать и запускать как есть.