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 actors FROM film AS f JOIN film_actor AS fa USING (film_id) JOIN actor AS a USING (actor_id) GROUP BY film_id HAVING every(a.first_name LIKE '%A%' ) |
Вышеуказанные выходы:
1
2
3
4
5
6
7
8
|
TITLE ACTORS ----------------------------------------------------- AMISTAD MIDSUMMER CARY, DARYL, SCARLETT, SALMA ANNIE IDENTITY CATE, ADAM, GRETA ANTHEM LUKE MILLA, OPRAH ARSENIC INDEPENDENCE RITA, CUBA, OPRAH BIRD 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" predicates having(Condition... conditions); having(Collection<? extends Condition> conditions); // These accept a BOOLEAN type having(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 API Condition 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" predicates having(Condition condition); having(Condition... conditions); having(Collection<? extends Condition> conditions); // These accept a BOOLEAN type having(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 . |