使用jOOQ 3.14合成外键在视图上写隐式连接

jOOQ支持JPQL最酷的功能之一已经有一段时间了:隐式连接。使用jOOQ,你可以以一种安全的方式浏览你的to-one关系,隐式生成LEFT JOIN 操作,而不必一直考虑连接谓词,以及正确的连接顺序。考虑一下这个Sakila数据库查询,如果SQL原生支持隐式连接。


SELECT
cu.first_name,
cu.last_name
FROM customer AS cu
WHERE cu.address.city.country.country = 'Switzerland'


它在本地SQL中被翻译成这个查询。


SELECT
cu.first_name,
cu.last_name
FROM customer AS cu
JOIN address AS ad ON cu.address_id = ad.address_id
JOIN city AS ci ON a.city_id = ci.city_id
JOIN country AS co ON ci.country_id = co.country_id
WHERE co.country = 'Switzerland'


**注意:**从jOOQ 3.14开始支持内联,这取决于外键是否是强制性的/非空。默认行为是产生LEFT JOIN ,这是隐式连接可选外键的正确方式。 隐式连接不是银弹。不是每一个JOIN 图都可以完全转化为隐式连接的用法,也不是每一个隐式连接的用法都比本地SQLJOIN 图更容易阅读。但是有这种选择是很好的。特别是,当你的键是复合键时。

视图上的经典连接

在经典的关系设计中,代理键经常被避免,而且我认为在很多情况下我们仍然应该避免它们。即使你不同意,你也可能偶尔会在一个模式上工作,那里很少甚至没有代用键。一个这样的例子是标准的SQLINFORMATION_SCHEMA ,例如在H2、HSQLDB、MariaDB、MySQL、PostgreSQL或SQL Server中实现的。例如,当查询HSQLDB的DOMAIN_CONSTRAINTS 视图以逆向工程DOMAIN类型。以前的jOOQ查询是这样的。


Domains d = DOMAINS.as("d");
DomainConstraints dc = DOMAIN_CONSTRAINTS.as("dc");
CheckConstraints cc = CHECK_CONSTRAINTS.as("cc");

for (Record record : create()
.select(
d.DOMAIN_SCHEMA,
d.DOMAIN_NAME,
d.DATA_TYPE,
d.CHARACTER_MAXIMUM_LENGTH,
d.NUMERIC_PRECISION,
d.NUMERIC_SCALE,
d.DOMAIN_DEFAULT,
cc.CHECK_CLAUSE)
.from(d)
.join(dc)
.on(row(d.DOMAIN_CATALOG, d.DOMAIN_SCHEMA, d.DOMAIN_NAME)
.eq(dc.DOMAIN_CATALOG, dc.DOMAIN_SCHEMA, dc.DOMAIN_NAME))
.join(cc)
.on(row(dc.CONSTRAINT_CATALOG,
dc.CONSTRAINT_SCHEMA,
dc.CONSTRAINT_NAME)
.eq(cc.CONSTRAINT_CATALOG,
cc.CONSTRAINT_SCHEMA,
cc.CONSTRAINT_NAME))
.where(d.DOMAIN_SCHEMA.in(getInputSchemata()))
.orderBy(d.DOMAIN_SCHEMA, d.DOMAIN_NAME)
) { ... }


所以,该查询加入了DOMAINS - DOMAIN_CONSTRAINTS - CHECK_CONSTRAINTS 之间的多对多关系,以获得生成DOMAIN类型所需的所有信息。这些视图是不可更新的,也没有任何与之相关的约束信息,但如果我们能够定义合成约束呢?jOOQ已经支持合成主键,以帮助使视图可更新。

合成外键

从jOOQ 3.14开始,我们重新设计了合成键的工作方式,商业版也将支持合成外键。 你可以像这样指定一个配置。


<configuration>
<generator>
<database>
<syntheticObjects>
<primaryKeys>
<primaryKey>
<tables>
CHECK_CONSTRAINTS|CONSTRAINTS|TABLE_CONSTRAINTS
</tables>
<fields>
<field>CONSTRAINT_(CATALOG|SCHEMA|NAME)</field>
</fields>
</primaryKey>
<primaryKey>
<tables>DOMAINS</tables>
<fields>
<field>DOMAIN_(CATALOG|SCHEMA|NAME)</field>
</fields>
</primaryKey>
</primaryKeys>
<foreignKeys>
<foreignKey>
<tables>DOMAIN_CONSTRAINTS</tables>
<fields>
<field>CONSTRAINT_(CATALOG|SCHEMA|NAME)</field>
</fields>
<referencedTable>CHECK_CONSTRAINTS</referencedTable>
</foreignKey>
<foreignKey>
<tables>DOMAIN_CONSTRAINTS</tables>
<fields>
<field>DOMAIN_(CATALOG|SCHEMA|NAME)</field>
</fields>
<referencedTable>DOMAINS</referencedTable>
</foreignKey>
</foreignKeys>
</syntheticObjects>
</database>
</generator>
</configuration>


而jOOQ的代码生成器已经会认为这些视图是表,实际上有下面这样的约束。


ALTER TABLE CHECK_CONSTRAINTS
ADD PRIMARY KEY (
CONSTRAINT_CATALOG, CONSTRAINT_SCHEMA, CONSTRAINT_NAME
);

ALTER TABLE DOMAINS
ADD PRIMARY KEY (
DOMAIN_CATALOG, DOMAIN_SCHEMA, DOMAIN_NAME
);

ALTER TABLE DOMAIN_CONSTRAINTS
ADD FOREIGN KEY (
CONSTRAINT_CATALOG, CONSTRAINT_SCHEMA, CONSTRAINT_NAME
)
REFERENCES CHECK_CONSTRAINTS;

ALTER TABLE DOMAIN_CONSTRAINTS
ADD FOREIGN KEY (
DOMAIN_CATALOG, DOMAIN_SCHEMA, DOMAIN_NAME
)
REFERENCES DOMAINS;


更复杂的配置是可能的,例如为约束分配名称,使用与表内排序不同的字段排序的复合约束,或者外键引用唯一键而不是主键。关于可用内容的完整描述,请参考手册。 有了上述可供代码生成器使用的合成元数据,所有众多的好东西现在在视图上也可用,包括。

让我们来看看

隐式连接

隐式连接现在也可以在这些视图上使用,也就是说。

  1. 你再也不用记住在连接谓词中包括所有的键列了(再见,意外的笛卡尔产品)。
  2. 即使你的复合键变成了别的东西,你的代码仍然是正确的

所以,这不仅仅是一个方便的事情,它也是一个正确性的事情。我们之前的查询现在可以这样写,以一种更简洁的方式。


DomainConstraints dc = DOMAIN_CONSTRAINTS.as("dc");

for (Record record : create()
.select(
dc.domains().DOMAIN_SCHEMA,
dc.domains().DOMAIN_NAME,
dc.domains().DATA_TYPE,
dc.domains().CHARACTER_MAXIMUM_LENGTH,
dc.domains().NUMERIC_PRECISION,
dc.domains().NUMERIC_SCALE,
dc.domains().DOMAIN_DEFAULT,
dc.checkConstraints().CHECK_CLAUSE)
.from(dc)
.where(dc.domains().DOMAIN_SCHEMA.in(getInputSchemata()))
.orderBy(dc.domains().DOMAIN_SCHEMA, dc.domains().DOMAIN_NAME)
) { ... }


注意我们是如何使用关系表作为唯一的表放在FROM 子句中的。这样,我们就可以从DOMAIN_CONSTRAINTS -> DOMAINSDOMAIN_CONSTRAINTS -> CHECK_CONSTRAINTS 两个方向的对一关系中进行导航。由此产生的SQL查询等同于之前的查询,但所有通过3列组合键连接的讨厌的东西都没有了。我个人认为这更有可读性。

未来的工作

到目前为止,只有一对一的关系可以用这种方式来导航。JPQL也提供对多关系的导航,但有一些限制。这是一个滑动的斜坡。当提供to-many关系时,一些用例是显而易见的,但其他的语义就不那么明显了。例如,让SELECT 子句根据投影列的存在产生更多(或更少)的行并不是一个好主意。这就是为什么到目前为止jOOQ对隐式连接只产生LEFT JOIN ,因为这将保证隐式连接的列不会因为INNER JOIN ,不产生任何匹配而减少行数。 尽管如此,在#7536中我们仍然可以添加很多东西,包括。

  • FROM 子句中的隐式对多连接,它们不会引起任何问题。
  • DML中的隐式连接
  • 支持解析器,以便在www.jooq.org/translate,并通过jOOQ的解析器向所有使用jOOQ SQL的人提供这一功能。 ParsingConnection

还有更多!

猜你喜欢

转载自juejin.im/post/7126382438971867149