Статьи

Конфигурация jOOQ

Эта статья является частью нашего академического курса под названием 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 .