一个长期存在的功能请求没有得到jOOQ社区的喜爱,尽管很多人可能想要它。它的标题是:让Table<R>
延伸到SelectField<R>
: github.com/jOOQ/jOOQ/i…
这个功能的具体含义是什么?
厉害的PostgreSQL
让我们来看看PostgreSQL的一个非常酷的功能。在PostgreSQL中,可以通过各种方式嵌套记录,包括在SELECT子句中简单地引用(非限定的)表名。使用sakila数据库,这就是有效的PostgreSQL:
SELECT DISTINCT actor, category
FROM actor
JOIN film_actor USING (actor_id)
JOIN film_category USING (film_id)
JOIN category USING (category_id)
这列出了所有的演员和他们所演的电影类别。这实际上只有一个意思,对吗?看起来好像是这个的语法糖,在所有的数据库产品上都有效:
SELECT DISTINCT actor.*, category.*
FROM actor
JOIN film_actor USING (actor_id)
JOIN film_category USING (film_id)
JOIN category USING (category_id)
但它有微妙的不同。结果看起来像这样,在psql中:
actor | category
-------------------------------------------------+---------------------------------------
... |
(1,PENELOPE,GUINESS,"2006-02-15 04:34:33") | (12,Music,"2006-02-15 04:46:27")
(1,PENELOPE,GUINESS,"2006-02-15 04:34:33") | (13,New,"2006-02-15 04:46:27")
(1,PENELOPE,GUINESS,"2006-02-15 04:34:33") | (14,Sci-Fi,"2006-02-15 04:46:27")
(1,PENELOPE,GUINESS,"2006-02-15 04:34:33") | (15,Sports,"2006-02-15 04:46:27")
(2,NICK,WAHLBERG,"2006-02-15 04:34:33") | (1,Action,"2006-02-15 04:46:27")
(2,NICK,WAHLBERG,"2006-02-15 04:34:33") | (2,Animation,"2006-02-15 04:46:27")
... |
如果你用DBeaver 来显示结果,你会看到一个类似的嵌套结构:
[
这里发生的情况是,PostgreSQL简单地将两个表嵌套为输出中的嵌套记录。这种表示方法与投射星号(*)有点不同,但在逻辑上是一样的(有一些细微的差别)。这不是很酷吗?你们中的一些人可能习惯于像这样使用ROW构造函数(其中ROW
关键字是可选的):
SELECT DISTINCT
ROW(actor_id, first_name, last_name) AS actor,
ROW(category_id, name) AS category
FROM actor
JOIN film_actor USING (actor_id)
JOIN film_category USING (film_id)
JOIN category USING (category_id)
这也会产生嵌套记录,尽管这次没有记录类型,来自psql:
actor | category
---------------------------+-----------------
... |
(1,PENELOPE,GUINESS) | (12,Music)
(1,PENELOPE,GUINESS) | (13,New)
(1,PENELOPE,GUINESS) | (14,Sci-Fi)
(1,PENELOPE,GUINESS) | (15,Sports)
(2,NICK,WAHLBERG) | (1,Action)
(2,NICK,WAHLBERG) | (2,Animation)
(2,NICK,WAHLBERG) | (3,Children)
(2,NICK,WAHLBERG) | (4,Classics)
或者,从DBeaver:
[
这可以在jOOQ中使用吗?
虽然Oracle/PostgreSQL的UDTs一直可用,但从jOOQ 3.15开始,这种从SELECT
子句中预测的临时嵌套记录表达式就可以在jOOQ中使用。就像伟大的MULTISET
操作符一样,它们是访问更强大的嵌套集合映射的关键。
但是从jOOQ 3.17开始,表表达式版本现在终于也可以访问了。在jOOQ中,以前来自PostgreSQL的SQL查询将转化为这个:
// Projecting table expressions
Result<Record2<ActorRecord, CategoryRecord>> result1 =
ctx.selectDistinct(ACTOR, CATEGORY)
.from(ACTOR)
.join(FILM_ACTOR).using(FILM_ACTOR.ACTOR_ID)
.join(FILM_CATEGORY).using(FILM_CATEGORY.FILM_ID)
.join(CATEGORY).using(CATEGORY.CATEGORY_ID)
.fetch();
// Projecting ad-hoc ROW expressions
Result<Record2<
Record3<Long, String, String>, // actor
Record2<Long, String> // category
>> result2 =
ctx.selectDistinct(
row(
ACTOR.ACTOR_ID,
ACTOR.FIRST_NAME,
ACTOR.LAST_NAME
).as("actor"),
row(CATEGORY.CATEGORY_ID, CATEGORY.NAME).as("category")
)
.from(ACTOR)
.join(FILM_ACTOR).using(FILM_ACTOR.ACTOR_ID)
.join(FILM_CATEGORY).using(FILM_CATEGORY.FILM_ID)
.join(CATEGORY).using(CATEGORY.CATEGORY_ID)
.fetch();
就像jOOQ 3.15一样,你也可以通过特设的转换器将jOOQ生成的记录转换为对你的目标消费者更有用的东西,例如Java 16record
类型。
与隐式连接相结合
jOOQ的一个非常强大的功能是隐式连接,它在jOOQ 3.11中被加入。也许,你觉得一直写显式连接语法不是很顺手?为什么不这样写呢:
Result<Record2<CustomerRecord, CountryRecord>> result =
ctx.select(
CUSTOMER,
CUSTOMER.address().city().country()
)
.from(CUSTOMER)
.fetch();
请注意,我们并没有投射CUSTOMER
或COUNTRY
表的任何单独的列,我们只是投射整个表,就像在PostgreSQL中一样,而且所有的类型都是安全的,在结果记录上有getters和setters。
注意事项
像往常一样,要知道你在做什么,以及为什么要这样做。在PostgreSQL和jOOQ中,投射CUSTOMER
表大多只是投射CUSTOMER.*
的糖,也就是说,你可能得到很多你不需要的数据。总会有一个便利/性能的权衡。理想情况下,如果你想经常使用这种方法,在你的数据库中创建视图,并为这些视图生成jOOQ代码。通过jOOQ中的合成外键,你仍然可以从视图的隐式连接语法中获益。