jOOQ — это внутренний предметно-ориентированный язык (DSL) , моделирующий язык SQL (внешний DSL) в Java (основной язык). Основной механизм API jOOQ описан в этой популярной статье:
Ускоренный курс по разработке API Java Fluent .
Любой может реализовать внутренний DSL на Java (или на большинстве других основных языков) в соответствии с правилами из этой статьи.
Пример функции языка SQL: BOOLEANs
Однако одна из приятных особенностей языка SQL — это тип BOOLEAN , который был введен в язык позднее, начиная с SQL: 1999 . Конечно, без логических значений вы можете просто смоделировать значения TRUE и FALSE через 1 и 0 и преобразовать предикаты в значение, используя CASE
|
1
|
CASE WHEN A = B THEN 1 ELSE 0 END |
Но с истинной поддержкой BOOLEAN вы можете выполнять удивительные запросы, такие как следующий запрос PostgreSQL, который выполняется к базе данных Sakila :
|
1
2
3
4
5
6
7
8
|
SELECT f.title, string_agg(a.first_name, ', ') AS actorsFROM film AS fJOIN film_actor AS fa USING (film_id)JOIN actor AS a USING (actor_id)GROUP BY film_idHAVING every(a.first_name LIKE '%A%') |
Вышеуказанные выходы:
|
1
2
3
4
5
6
7
8
|
TITLE ACTORS-----------------------------------------------------AMISTAD MIDSUMMER CARY, DARYL, SCARLETT, SALMAANNIE IDENTITY CATE, ADAM, GRETAANTHEM LUKE MILLA, OPRAHARSENIC INDEPENDENCE RITA, CUBA, OPRAHBIRD INDEPENDENCE FAY, JAYNE... |
Другими словами, мы ищем все фильмы, в которых все актеры, сыгравшие в фильме, содержат букву «А» в своих именах. Это делается с помощью агрегации по логическому выражению / предикату first_name LIKE '%A%' :
|
1
|
HAVING every(a.first_name LIKE '%A%') |
Теперь, с точки зрения API jOOQ, это означает, что нам придется предоставлять перегрузки метода have having() которые принимают разные типы аргументов, такие как:
|
1
2
3
4
5
6
|
// These accept "classic" predicateshaving(Condition... conditions);having(Collection<? extends Condition> conditions);// These accept a BOOLEAN typehaving(Field<Boolean> condition); |
Конечно, эти перегрузки доступны для любого метода API, который принимает предикаты / логические значения, а не только для предложения HAVING .
Как упоминалось ранее, начиная с SQL: 1999, Condition и Field<Boolean> jOOQ Field<Boolean> — это одно и то же. jOOQ позволяет конвертировать между ними через явный API:
|
1
2
3
|
Condition condition1 = FIRST_NAME.like("%A%");Field<Boolean> field = field(condition1);Condition condition2 = condition(field); |
… А перегрузки делают преобразование более удобным для использования.
Так в чем проблема?
Проблема в том, что мы подумали, что было бы неплохо добавить еще одну удобную перегрузку — метод having(Boolean) метод, в котором для удобства могут быть введены постоянные, обнуляемые значения BOOLEAN в запросе, что может быть полезно при построении динамического SQL. или комментируя некоторые предикаты:
|
1
2
3
4
5
6
7
8
|
DSL.using(configuration) .select() .from(TABLE) .where(true)// .and(predicate1) .and(predicate2)// .and(predicate3) .fetch(); |
Идея заключается в том, что ключевое слово WHERE никогда не будет закомментировано, независимо от того, какой предикат вы хотите временно удалить.
К сожалению, добавление этой перегрузки создало неудобства для разработчиков, использующих автозаполнение IDE. Рассмотрим следующие два вызова метода:
|
1
2
3
4
5
6
|
// Using jOOQ APICondition condition1 = FIRST_NAME.eq ("ADAM");Condition condition2 = FIRST_NAME.equal("ADAM");// Using Object.equals (accident)boolean = FIRST_NAME.equals("ADAM"); |
(Случайно) добавив букву «s» в метод equal() — в основном из-за автодополнения IDE — все выражение предиката резко меняет семантику, начиная с элемента дерева выражений jOOQ, который можно использовать для генерации SQL, до «обычного» логического значения значение (которое всегда дает false , очевидно).
До добавления последней перегрузки это не было проблемой. Использование метода equals() не будет компилироваться, поскольку не было применимой перегрузки, принимающей boolean тип Java.
|
01
02
03
04
05
06
07
08
09
10
|
// These accept "classic" predicateshaving(Condition condition);having(Condition... conditions);having(Collection<? extends Condition> conditions);// These accept a BOOLEAN typehaving(Field<Boolean> condition);// This method didn't exist prior to jOOQ 3.7// having(Boolean condition); |
После jOOQ 3.7 эта авария стала незаметно в пользовательском коде, так как компилятор больше не жаловался, что привело к неправильному SQL.
Вывод: будьте осторожны при разработке внутреннего DSL. Вы наследуете «недостатки» языка хоста
Java имеет «недостатки» в том, что каждый тип гарантированно наследуется от java.lang.Object и вместе с ним его методов: getClass() , clone() , finalize() equals() , hashCode() , toString() , notify() , notifyAll() и wait() .
В большинстве API это не такая уж большая проблема. Вам на самом деле не нужно повторно использовать любое из названных выше методов (пожалуйста, не используйте) .
Но при разработке внутреннего DSL эти имена методов Object (как и ключевые слова языка) ограничивают вас в пространстве разработки. Это особенно очевидно в случае equal(s) .
Мы узнали, и мы устарели и удалим having(Boolean) перегрузку и все подобные перегрузки снова.
| Ссылка: | Любопытный случай с недостатком дизайна jOOQ API от нашего партнера по JCG Лукаса Эдера в блоге JAVA, SQL и AND JOOQ . |