用jOOQ 3.17投射类型安全的嵌套表记录

一个长期存在的功能请求没有得到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();

请注意,我们并没有投射CUSTOMERCOUNTRY 表的任何单独的列,我们只是投射整个表,就像在PostgreSQL中一样,而且所有的类型都是安全的,在结果记录上有getters和setters。

注意事项

像往常一样,要知道你在做什么,以及为什么要这样做。在PostgreSQL和jOOQ中,投射CUSTOMER 表大多只是投射CUSTOMER.* 的糖,也就是说,你可能得到很多你不需要的数据。总会有一个便利/性能的权衡。理想情况下,如果你想经常使用这种方法,在你的数据库中创建视图,并为这些视图生成jOOQ代码。通过jOOQ中的合成外键,你仍然可以从视图的隐式连接语法中获益。

猜你喜欢

转载自juejin.im/post/7126043618292269064
今日推荐