Статьи

Интеграция jOOQ с PostgreSQL: создание разделов

Вступление

jOOQ — это отличный фреймворк, когда вы хотите работать с SQL в Java без чрезмерного использования ORM. В то же время его можно интегрировать во многие среды, поскольку он предлагает вам поддержку многих специфичных для базы данных функций. Одной из таких специфичных для базы данных функций является разбиение в PostgreSQL. Разбиение в PostgreSQL в основном используется из соображений производительности, поскольку в определенных ситуациях это может повысить производительность запросов. У jOOQ нет явной поддержки этой функции, но ее можно легко интегрировать, как мы вам покажем.

Эта статья предоставлена ​​вам немецким партнером по интеграции JOOQ UWS Software Service (UWS) . UWS специализируется на разработке программного обеспечения, модернизации приложений и аутсорсинге, уделяя особое внимание экосистеме Java Enterprise.

Разбиение в PostgreSQL

Благодаря функции разделения PostgreSQL у вас есть возможность разбить данные, которые бы образовали огромную таблицу, на несколько отдельных таблиц. Каждый из разделов является нормальной таблицей, которая наследует свои столбцы и ограничения от родительской таблицы. Это так называемое наследование таблиц может использоваться для «разделения диапазона», когда, например, данные из одного диапазона не перекрывают данные из другого диапазона с точки зрения идентификаторов, дат или других критериев.

Как и в следующем примере, у вас может быть разбиение для таблицы «author», которая использует один и тот же внешний ключ таблицы «authorgroup» во всех ее строках.

CREATE TABLE author (
  authorgroup_id int,
  LastName varchar(255)
);
 
CREATE TABLE author_1 (
  CONSTRAINT authorgroup_id_check_1
    CHECK ((authorgroup_id = 1))
) INHERITS (author);
 
CREATE TABLE author_2 (
  CONSTRAINT authorgroup_id_check_2
    CHECK ((authorgroup_id = 2))
) INHERITS (author);
 
...

Как вы можете видеть, мы настраиваем наследование и — чтобы иметь простой пример — мы просто помещаем одно ограничение, проверяющее, что у разделов одинаковый «authorgroup_id». В основном это приводит к тому, что таблица «author» содержит только определения таблиц и столбцов, но не содержит данных. Однако при запросе к таблице «author» PostgreSQL действительно запрашивает все наследующие таблицы «author_n», возвращая объединенный результат.

Тривиальный подход к использованию jOOQ с разметкой

Для работы с разделами, описанными выше, jOOQ предлагает несколько вариантов. Вы можете использовать способ по умолчанию, который позволяет jOOQ генерировать один класс на таблицу. Чтобы вставить данные в несколько таблиц, вам придется использовать разные классы. Этот подход используется в следующем фрагменте:

// add
InsertQuery query1 = dsl.insertQuery(AUTHOR_1);
query1.addValue(AUTHOR_1.ID, 1);
query1.addValue(AUTHOR_1.LAST_NAME, "Nowak");
query1.execute();
 
InsertQuery query2 = dsl.insertQuery(AUTHOR_2);
query2.addValue(AUTHOR_2.ID, 1);
query2.addValue(AUTHOR_2.LAST_NAME, "Nowak");
query2.execute();
 
// select
Assert.assertTrue(dsl
    .selectFrom(AUTHOR_1)
    .where(AUTHOR_1.LAST_NAME.eq("Nowak"))
    .fetch().size() == 1);
 
Assert.assertTrue(dsl
    .selectFrom(AUTHOR_2)
    .where(AUTHOR_2.LAST_NAME.eq("Nowak"))
    .fetch().size() == 1);

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

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

Использование jOOQ с разметкой и мультитенантностью

Отображение схемы времени выполнения JOOQ часто используется для реализации сред баз данных, например, когда во время разработки запрашивается одна база данных, но при развертывании в рабочей среде запросы направляются в другую базу данных. Многопользовательский режим — это еще один рекомендуемый вариант использования для сопоставления схемы времени выполнения, поскольку он позволяет строго разделить разделы и настроить приложение на использование только тех баз данных или таблиц, которые настроены в сопоставлении схемы времени выполнения. Таким образом, выполнение одного и того же кода приведет к работе с разными базами данных или таблицами в зависимости от конфигурации, что позволяет обеспечить истинное разделение данных с точки зрения многопользовательского режима.

Следующая конфигурация, взятая из документации jOOQ, выполняется при создании DSLContext, поэтому ее можно рассматривать как общесистемный параметр:

Settings settings = new Settings()
  .withRenderMapping(new RenderMapping()
  .withSchemata(
      new MappedSchema().withInput("DEV")
                        .withOutput("MY_BOOK_WORLD")
                        .withTables(
      new MappedTable().withInput("AUTHOR")
                       .withOutput("AUTHOR_1"))));
 
// Add the settings to the Configuration
DSLContext create = DSL.using(
  connection, SQLDialect.ORACLE, settings);
 
// Run queries with the "mapped" configuration
create.selectFrom(AUTHOR).fetch();
 
// results in SQL:
// “SELECT * FROM MY_BOOK_WORLD.AUTHOR_1”

Используя этот подход, вы можете навсегда отобразить одну таблицу на один раздел, например. «AUTHOR» — «AUTHOR_1» для среды «DEV». В другой среде вы можете выбрать отображение таблицы «AUTHOR» на «AUTHOR_2».

Сопоставление со схемой выполнения позволяет отображать только одну таблицу на основе запроса, поэтому вы не можете обработать сценарий использования, в котором вы захотите манипулировать несколькими разделами таблицы. Если вы хотите иметь больше гибкости, вы можете рассмотреть следующий подход.

Использование jOOQ с разметкой и без многопользовательского режима

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

// add
for(int i=1; i<=2; i++) {
  Builder part = forPartition(i);
  InsertQuery query = dsl.insertQuery(part.table(AUTHOR));
  query.addValue(part.field(AUTHOR.ID), 1);
  query.addValue(part.field(AUTHOR.LAST_NAME), "Nowak");
  query.execute();
}
 
// select
 
for(int i=1; i<=2; i++) {
  Builder part = forPartition(i);
  Assert.assertTrue(dsl
      .selectFrom(part.table(AUTHOR))
      .where(part.field(AUTHOR.LAST_NAME).eq("Nowak"))
      .fetch()
      .size() == 1);
}

Вы можете видеть выше, что номера разделов абстрагированы, поэтому вы можете использовать таблицу «AUTHOR» вместо «AUTHOR_1». Таким образом, ваш код не будет загрязнен многими сгенерированными классами. Другое дело, что объект разделителя инициализируется динамически, поэтому вы можете использовать его, например, в цикле, как описано выше. Также он следует шаблону Builder, так что вы можете работать с ним так, как вы привыкли к jOOQ.

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

Интеграция разделов jOOQ без многопользовательского режима в процесс сборки Maven (необязательно)

Если вы используете Continuous-Integration, вы можете интегрировать решение выше, чтобы jOOQ не генерировал таблицы для секционированных таблиц. Это может быть достигнуто с помощью регулярного выражения, которое исключает определенные имена таблиц при создании классов Java. При использовании Maven ваша интеграция может выглядеть примерно так:

<generator>
  <name>org.jooq.util.DefaultGenerator</name>
  <database>
    <name>org.jooq.util.postgres.PostgresDatabase</name>
    <includes>.*</includes>
    <excludes>.*_[0-9]+</excludes>
    <inputSchema>${db.schema}</inputSchema>
  </database>
  <target>
    <packageName>com.your.company.jooq</packageName>
    <directory>target/generated-sources/jooq</directory>
  </target>
</generator>

Затем он просто вызывает mvn install и плагин jOOQ maven будет генерировать схему базы данных во время компиляции.

Интеграция jOOQ с PostgreSQL: создание разделов

В этой статье описывается, как jOOQ в сочетании с функцией разделения PostgreSQL можно использовать для реализации многопользовательского режима и повышения производительности базы данных. Документация PostgreSQL гласит, что для разделения «преимущества, как правило, имеют смысл только тогда, когда таблица в противном случае будет очень большой. Точная точка, в которой таблица получит выгоду от разделения, зависит от приложения, хотя практическое правило заключается в том, что размер таблицы должен превышать физическую память сервера базы данных ».

Обеспечение поддержки разбиения с помощью jOOQ так же просто, как добавление конфигурации или небольшого служебного класса, затем jOOQ может поддерживать разбиение с использованием нескольких арендаторов или без них и без ущерба для безопасности типов. Помимо интеграции на уровне Java, описанное решение также легко интегрируется в процесс сборки и тестирования.

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

Пожалуйста, дайте нам знать, если вам нужна поддержка для этой или другой интеграции JOOQ в вашей среде. UWS Software Service (UWS) является официальным партнером по интеграции jOOQ .