Эта статья является частью нашего академического курса под названием jOOQ — тип безопасного запроса в БД .
jOOQ — хороший выбор в приложении Java, где важны SQL и конкретная реляционная база данных. Это альтернатива, когда JPA / Hibernate слишком много абстрагирует, JDBC слишком мало. Он показывает, как современный предметно-ориентированный язык может значительно повысить производительность труда разработчиков, внедряя SQL в Java.
В этом курсе мы увидим, как мы можем эффективно запрашивать базы данных, используя jOOQ. Проверьте это здесь !
Содержание
1. Введение
Существует множество SPI ( интерфейсов поставщиков услуг ), которые позволяют управлять жизненным циклом различных объектов в jOOQ. Эти SPI могут быть введены через объект Configuration
. В этом разделе мы узнаем, как эти объекты управления жизненным циклом взаимодействуют с jOOQ.
Примеры, показанные в этом разделе, также доступны из пакета org.jooq.academy.section4 .
2. ConnectionProvider
Самый важный SPI — это тот, который предоставляет jOOQ соединение JDBC. До сих пор в примерах Connection
передавался непосредственно DSL.using()
:
1
|
DSLContext dsl = DSL.using(connection); |
Эта нотация является просто удобством для более подробного варианта, где connection
заключено в DefaultConnectionProvider
:
1
|
DSLContext dsl = DSL.using( new DefaultConfiguration().set( new DefaultConnectionProvider(connection))); |
В более сложных настройках, чем в этом руководстве, вы можете вместо этого предоставить jOOQ источник данных, например, при использовании пула Connection
или даже при использовании распределенных транзакций через JTA. Популярные DataSources
использования DataSources
поддерживаются нативно, в том числе с помощью удобных методов (хотя вам необходимо предоставить SQLDialect
, поскольку его нельзя получить из DataSource
1
|
DSLContext dsl = DSL.using(dataSource, SQLDialect.H2); |
Если вы хотите реализовать любой другой тип источника JDBC- Connection
, вы можете реализовать свой собственный ConnectionProvider
1
2
3
4
5
6
7
|
public interface ConnectionProvider { // jOOQ will acquire a connection through this method prior to query execution Connection acquire() throws DataAccessException; // jOOQ will release previously acquired connections again through this method after query execution void release(Connection connection) throws DataAccessException; } |
3. SQLDialect
jOOQ сгенерирует и выполнит ваш оператор SQL в контексте конкретного SQLDialect. Это может быть лучше всего проиллюстрировано на примере при запуске следующей программы:
1
2
3
4
|
// This renders SELECT 1 / SELECT 1 FROM DUAL in various SQL dialect families Arrays.stream(SQLDialect.families()) .map(family -> String.format( "%9s : " , family) + DSL.using(family).render(DSL.selectOne())) .forEach(System.out::println); |
Когда вы выполняете вышеуказанную программу, вы можете получить что-то вроде следующего:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
ACCESS : select 1 as [one] from (select count(*) dual from MSysResources) as dual ASE : select 1 [one] CUBRID : select 1 "one" from "db_root" DB2 : select 1 "one" from "SYSIBM" . "DUAL" DERBY : select 1 as "one" from "SYSIBM" . "SYSDUMMY1" FIREBIRD : select 1 "one" from "RDB$DATABASE" H2 : select 1 "one" from dual INFORMIX : select 1 "one" from (select 1 as dual from systables where tabid = 1 ) as dual INGRES : select 1 "one" from (select 1 as dual) as dual HSQLDB : select 1 as "one" from "INFORMATION_SCHEMA" . "SYSTEM_USERS" MARIADB : select 1 as `one` from dual MYSQL : select 1 as `one` from dual ORACLE : select 1 "one" from dual POSTGRES : select 1 as "one" SQLITE : select 1 one SQLSERVER : select 1 [one] SYBASE : select 1 [one] from [SYS].[DUMMY] |
Все операторы были сгенерированы из одного и того же выражения DSL DSL.selectOne()
. В большинстве случаев вам не нужно беспокоиться о небольших различиях между различными диалектами SQL, поскольку jOOQ будет абстрагировать их от вас в одном API.
Поддерживается ли какой-либо данный элемент API jOOQ вашим SQLDialect
можно увидеть из аннотации @Support
для большинства методов DSL. Возьмите метод DSL.denseRank()
, который моделирует оконную функцию DENSE_RANK()
. Он объявлен в API jOOQ как таковой:
1
2
|
@Support ({ CUBRID, DB2, INFORMIX, POSTGRES, ORACLE, SQLSERVER, SYBASE }) public static WindowOverStep<Integer> rank() { ... } |
4. Настройки
Настройки используются для предоставления jOOQ информации об общем отображении запросов и поведении их выполнения. Они регулируются XSD, который доступен здесь: http://www.jooq.org/xsd/jooq-runtime-3.3.0.xsd (проверьте последние руководства или веб-сайт на наличие потенциальных обновлений)
В текущей версии настройки jOOQ содержат флаги для управления…
- Должны ли таблицы полностью соответствовать схеме
- Должны ли таблицы и схемы быть переведены / отображены (например, для реализации мультитенантности)
- Следует ли заключать в кавычки имена схем, таблиц и столбцов (например, для поддержки чувствительных к регистру имен)
- Должны ли генерируемые ключевые слова SQL быть прописными или строчными
- Нужно ли форматировать сгенерированный SQL (например, для ведения журнала отладки)
- Должны ли значения привязки отображаться в виде вопросительных знаков, именованных параметров или встроенных
- Должны ли быть выполнены статические или подготовленные операторы
- Активна ли регистрация выполнения
- Активна ли оптимистическая блокировка
- Должны ли активные записи сохранять ссылку на
Configuration
которая их произвела - Есть ли у активных записей обновляемые первичные ключи
- Следует ли кэшировать информацию об отражении
5. ExecuteListeners
ExecuteListener
— это один из пары SPI ( Service Provider Interface), который вы можете использовать, чтобы подключиться к высокому уровню рендеринга запросов jOOQ, привязки переменных и жизненного цикла выполнения. В следующем примере показан простой способ измерения времени выполнения запроса для каждого запроса.
5.1. Пример ExecuteListener
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
ExecuteListener listener = new DefaultExecuteListener() { @Override public void start(ExecuteContext ctx) { // Register the start time to the current context ctx.data( "time" , System.nanoTime()); } @Override public void end(ExecuteContext ctx) { // Extract the start time from the current context Long time = (Long) ctx.data( "time" ); System.out.println( "Execution time : " + ((System.nanoTime() - time) / 1000 / 1000.0 ) + "ms. Query : " + ctx.sql()); } }; |
Этот слушатель может затем использоваться в Configuration
следующим образом:
1
2
3
4
5
6
7
8
|
DSL.using( new DefaultConfiguration() .set(SQLDialect.H2) .set( new DefaultConnectionProvider(connection)) .set( new DefaultExecuteListenerProvider(listener)) ) .select(AUTHOR.ID) .from(AUTHOR) .fetch(); |
Вызов fetch()
теперь будет инициировать весь жизненный цикл выполнения запроса, включая реализованные обратные вызовы start()
и end()
. Это приведет к следующему выводу на консоль:
1
|
Execution time : 0 .101ms. Query : select "PUBLIC" . "AUTHOR" . "ID" from "PUBLIC" . "AUTHOR" |
Существуют и другие SPI для конкретных случаев использования. Подробнее о них см. В руководстве по jOOQ .