一个长期存在的功能请求没有得到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 16 record
类型。
与隐式连接相结合
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中的合成外键,你仍然可以从视图的隐式连接语法中获益。