jOOQ是一种内部领域特定语言(DSL) ,它以Java(宿主语言)建模SQL语言(外部DSL)。 这篇热门文章描述了jOOQ API的主要机制:
Java Fluent API设计器速成课程 。
任何人都可以根据该文章中的规则以Java(或大多数其他主机语言)实现内部DSL。
SQL语言功能示例:BOOLEAN
但是,关于SQL语言的优点之一是BOOLEAN
类型,该类型在SQL:1999以后才引入到该语言中。 当然,没有布尔值,您可以通过1
和0
建模TRUE
和FALSE
值,并使用CASE
将谓词转换为值
CASE WHEN A = B THEN 1 ELSE 0 END
但是有了真正的BOOLEAN
支持,您可以进行出色的查询,例如针对Sakila数据库运行的以下PostgreSQL查询:
SELECTf.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%')
以上收益:
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
...
换句话说,我们正在寻找所有电影,其中在电影中扮演过的所有演员的名字中都包含字母“ A”。 这是通过布尔表达式/谓词first_name LIKE '%A%'
上的聚合来完成first_name LIKE '%A%'
:
HAVING every(a.first_name LIKE '%A%')
现在,按照jOOQ API的术语,这意味着我们将不得不提供having()
不同参数类型的hading having()
方法的重载,例如:
// 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开始,jOOQ的Condition
和Field<Boolean>
确实是同一回事。 jOOQ允许通过显式API在两者之间进行转换:
Condition condition1 = FIRST_NAME.like("%A%");
Field<Boolean> field = field(condition1);
Condition condition2 = condition(field);
…和重载使转换更方便地隐式进行。
所以有什么问题?
问题在于,我们认为添加另一个方便的重载,即having(Boolean)
方法可能是个好主意,在having(Boolean)
方法中,为了方便起见,可以在查询中引入常量,可为空的BOOLEAN
值,这在构建动态SQL时很有用。 ,或注释掉某些谓词:
DSL.using(configuration).select().from(TABLE).where(true)
// .and(predicate1).and(predicate2)
// .and(predicate3).fetch();
这个想法是,无论您要临时删除哪个谓词,都不会将WHERE
关键字注释掉。
不幸的是,添加此重载给使用IDE自动完成功能的开发人员带来了麻烦。 考虑以下两个方法调用:
// Using jOOQ API
Condition condition1 = FIRST_NAME.eq ("ADAM");
Condition condition2 = FIRST_NAME.equal("ADAM");// Using Object.equals (accident)
boolean = FIRST_NAME.equals("ADAM");
通过(偶然地)在equal()
方法中添加字母“ s” equal()
主要是由于IDE自动补全),整个谓词表达式从可用于生成SQL的jOOQ表达式树元素到“普通”布尔值,极大地改变了语义。值(显然总是产生false
)。
在添加最后一个重载之前,这不是问题。 equals()
方法的用法无法编译,因为没有适用的重载采用Java boolean
类型。
// 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,这种事故开始在用户代码中引起注意。
您继承了宿主语言的“缺陷”
Java是“有缺陷的”,因为保证每种类型都可以继承自java.lang.Object
及其方法: getClass()
, clone()
, finalize()
equals()
, hashCode()
, toString()
, notify()
, notifyAll()
和wait()
。
在大多数API中,这实际上并不是什么大问题。 您实际上并不需要重用上述任何方法名(请不要) 。
但是在设计内部DSL时,这些Object
方法名称(就像language关键字一样)限制了您的设计空间。 在equal(s)
的情况下尤其明显。
我们已经学习,并且已经弃用,并且将移除having(Boolean)
重载 ,并再次移除所有类似的重载。
翻译自: https://www.javacodegeeks.com/2016/01/curious-incidence-jooq-api-design-flaw.html