При использовании jOOQ для создания динамических операторов SQL (одно из ключевых предложений jOOQ) часто бывает необходимо добавить элементы запроса условно с поведением по умолчанию «No-op». Для начинающих пользователей это поведение по умолчанию «no-op» не всегда очевидно, так как API jOOQ обширен, и, как и в случае любого обширного API, есть много разных вариантов для выполнения похожих вещей.
Как не сделать это
Общая ловушка — это соблазн работать со многими XYZStep
типами. Какие это типы? Как правило, они невидимы для разработчика, поскольку разработчики свободно используют DSL API jOOQ, как и JDK Stream API. Например:
Джава
xxxxxxxxxx
1
DSLContext ctx = ...;
2
Result<?> result =
4
ctx.select(T.A, T.B)
5
.from(T)
6
.where(T.C.eq(1))
7
.and(T.D.eq(2))
8
.fetch();
Давайте разберем приведенный выше запрос, чтобы увидеть, что происходит в API. Мы можем присвоить каждому результату метода локальную переменную:
Джава
xxxxxxxxxx
1
SelectFromStep<?> s1 = ctx.select(T.A, T.B);
2
SelectWhereStep<?> s2 = s1.from(T);
3
SelectConditionStep<?> s3 = s2.where(T.C.eq(1));
4
SelectConditionStep<?> s4 = s3.and(T.D.eq(2))
5
Result<?> result = s4.fetch();
В нашем предыдущем блоге, посвященном разработке API, объясняется эта техника разработки API .
Это не то, что обычно делают люди с операторами «статического SQL», но у них может возникнуть соблазн сделать это, если они захотят добавить последний предикат ( T.D = 2
) условно, например:
Джава
xxxxxxxxxx
1
DSLContext ctx = ...;
2
SelectConditionStep<?> c =
4
ctx.select(T.A, T.B)
5
.from(T)
6
.where(T.C.eq(1));
7
if (something)
9
c = c.and(T.D.eq(2));
10
Result<?> result = c.fetch();
Это совершенно правильное использование API, но мы не рекомендуем его, потому что оно очень грязное и ведет к затруднению обслуживания клиентского кода. Кроме того, это абсолютно не нужно, потому что есть лучший способ:
Составление запросов из его частей
Проблема с вышеуказанным подходом состоит в том, что он пытается использовать императивный подход, добавляя вещи в запрос шаг за шагом. Это - то, как много разработчиков имеют тенденцию структурировать свой код, но с SQL (и, как следствие, jOOQ), который может оказаться трудным сделать правильно. Функциональный подход имеет тенденцию работать лучше .
Обратите внимание, что не только вся структура DSL может быть назначена локальным переменным, но также и отдельные SELECT
аргументы предложения. Например:
Джава
xxxxxxxxxx
1
DSLContext ctx = ...;
2
List<SelectField<?>> select = Arrays.asList(T.A, T.B);
4
Table<?> from = T;
5
Condition where = T.C.eq(1).and(T.D.eq(2));
6
Result<?> result =
8
ctx.select(select)
9
.from(from)
10
.where(where)
11
.fetch();
Фактически, каждый запрос jOOQ является динамическим запросом SQL. Многие запросы просто выглядят как статические из-за дизайна API jOOQ.
Опять же, мы не будем присваивать каждый SELECT
аргумент предложения локальной переменной, только действительно динамические. Например:
Джава
xxxxxxxxxx
1
DSLContext ctx = ...;
2
Condition where = T.C.eq(1);
4
if (something)
6
where = where.and(T.D.eq(2));
7
Result<?> result =
9
ctx.select(T.A, T.B)
10
.from(T)
11
.where(where)
12
.fetch();
Это уже выглядит вполне прилично.
Избегайте нарушения читабельности
Многим людям не нравится этот подход, потому что он нарушает читабельность запроса, делая его компоненты нелокальными. Предикаты в запросе объявляются заранее, отдельно от самого запроса. Это не так, как многие люди любят рассуждать о SQL.
И вам не нужно! Вполне возможно встроить условие непосредственно в WHERE
предложение следующим образом:
Джава
xxxxxxxxxx
1
DSLContext ctx = ...;
2
Result<?> result =
4
ctx.select(T.A, T.B)
5
.from(T)
6
// We always need this predicate
8
.where(T.C.eq(1))
9
// This is only added conditionally
11
.and(something
12
? T.D.eq(2)
13
: DSL.noCondition())
14
.fetch();
Магия заключается в использовании выше DSL.noCondition
, который является псевдо-предикатом, который не генерирует никакого контента. Это заполнитель, где org.jooq.Condition
тип требуется без фактической реализации.
Существует также:
DSL.trueCondition
:TRUE
или1 = 1
в SQL - идентификатор дляAND
сокращения операций.DSL.falseCondition
:FALSE
или1 = 0
в SQL, идентификатор дляOR
сокращения операций
... но для этого нужно постоянно думать об этих идентичностях и сокращениях. Кроме того, если вы добавляете многие из них trueCondition()
или falseCondition()
к запросу, результирующий SQL имеет тенденцию быть довольно уродливым, например, для людей, которым приходится анализировать производительность на производстве. noCondition()
просто никогда не генерирует никакого контента вообще.
Обратите внимание , что
noCondition()
это не действует как личность! Если вашnoCondition()
единственный предикат, оставленный вWHERE
предложении, никакогоWHERE
предложения не будет , независимо от того, работаете ли вы сAND
предикатами илиOR
предикатами.
Безоперационные выражения в jOOQ
При использовании динамического SQL, подобного этому, и добавлении условий в запросы, такие «неоперативные выражения» становятся обязательными. В предыдущем примере мы видели, как добавить «предикат no-op» к WHERE
предложению (очевидно, что тот же подход будет работать и со HAVING
всеми другими предложениями, которые работают с логическими выражениями).
Три наиболее важных типа запросов jOOQ:
org.jooq.Field
: для выражений столбцовorg.jooq.Condition
: для логических выражений / предикатов столбцовorg.jooq.Table
: для табличных выражений
Пользователи могут захотеть добавить все эти условия в запросы.
org.jooq.Condition
Мы уже видели, как это сделать org.jooq.Condition
.
org.jooq.Field
А как насчет динамических выражений столбцов в проекции ( SELECT
предложение)? Предполагая, что вы хотите проецировать столбцы только в определенных случаях. В нашем примере T.B
столбец - это то, что нам не всегда нужно. Это легко! Можно использовать тот же подход (при условии, T.B
что это строковый столбец):
Джава
xxxxxxxxxx
1
DSLContext ctx = ...;
2
Result<Record2<String, String>> result =
4
ctx.select(T.A, something ? T.B : DSL.inline("").as(T.B))
5
.from(T)
6
.where(T.C.eq(1))
7
.and(T.D.eq(2))
8
.fetch();
Используя встроенные параметры через DSL.inline () , вы можете легко создать в своей проекции значение no-op, если вы не хотите изменять тип строки проекции. Преимущество состоит в том, что теперь вы можете использовать этот подзапрос в объединении, которое ожидает два столбца:
Джава
xxxxxxxxxx
1
DSLContext ctx = ...;
2
Result<Record2<String, String>> result =
4
// First union subquery has a conditionally projected column
6
ctx.select(T.A, something ? T.B : DSL.inline("").as(T.B))
7
.from(T)
8
.where(T.C.eq(1))
9
.and(T.D.eq(2))
10
.union(
12
// Second union subquery has no such conditions
14
select(U.A, U.B)
15
.from(U))
16
.fetch();
Вы можете сделать еще один шаг вперед и сделать таким образом весь подзапрос объединения условным!
Джава
xxxxxxxxxx
1
DSLContext ctx = ...;
2
Result<Record2<String, String>> result =
4
// First union subquery has a conditionally projected column
6
ctx.select(T.A, T.B)
7
.from(T)
8
.union(
9
something
10
? select(U.A, U.B).from(U)
11
: select(inline(""), inline("")).where(falseCondition())
12
)
13
.fetch();
Это немного более синтаксическая работа, но приятно видеть, как легко добавить что-то условно в запрос jOOQ, не делая запрос полностью нечитаемым. Все локально там, где оно используется. Локальные переменные не требуются, поток управления не вызывается.
И поскольку теперь все является выражением (а не оператором / отсутствием потока управления), мы можем выделить отдельные части этого запроса во вспомогательные методы, которые можно сделать повторно используемыми.
org.jooq.Table
Условные табличные выражения обычно появляются при выполнении условных объединений. Обычно это делается не изолированно, а вместе с другими условными элементами в запросе. Например, если некоторые столбцы проецируются условно, для этих столбцов может потребоваться дополнительное объединение, поскольку они происходят из другой таблицы, чем таблицы, которые используются безоговорочно. Например:
Джава
1
DSLContext ctx = ...;
2
Result<?> result =
4
ctx.select(
5
T.A,
6
T.B,
7
something ? U.X : inline(""))
8
.from(
9
something
10
? T.join(U).on(T.Y.eq(U.Y))
11
: T)
12
.where(T.C.eq(1))
13
.and(T.D.eq(2))
14
.fetch();
15
Существует не более простой способ , чтобы произвести условное JOIN
выражение, так как JOIN
и ON
необходимость быть обеспечены независимо друг от друга. Для простых случаев, как показано выше, это прекрасно. В более сложных случаях могут потребоваться некоторые вспомогательные методы или представления.
Заключение
Здесь есть два важных сообщения:
- Типы XYZStep являются только вспомогательными типами . Они предназначены для того, чтобы ваш динамически построенный оператор SQL выглядел как статический SQL. Но вы никогда не должны чувствовать необходимость присваивать их локальным переменным или возвращать их из методов. Хотя это не является ошибкой, почти всегда есть лучший способ написания динамического SQL.
- В jOOQ каждый запрос является динамическим запросом . Это является преимуществом составления запросов SQL с использованием дерева выражений, подобного тому, которое используется во внутренностях jOOQ. Вы можете не увидеть дерево выражений, поскольку API-интерфейс jOOQ DSL имитирует статический синтаксис оператора SQL. Но за кулисами вы эффективно строите это дерево выражений. Каждая часть дерева выражений может быть создана динамически из локальных переменных, методов или выражений, таких как условные выражения. Я с нетерпением жду использования новых выражений переключателя JEP 361 в динамическом SQL. Подобно
CASE
выражению SQL , некоторые части операторов SQL могут быть сконструированы динамически в клиенте перед передачей их на сервер.
Как только эти две вещи будут усвоены, вы сможете написать очень интересный динамический SQL, в том числе используя подходы FP для построения структур данных, таких как объект запроса jOOQ .