可用版本: Dev (3.21) | 最新 (3.20) | 3.19

隐式多对一路径JOIN

适用于 ✅ 开源版   ✅ 专业版   ✅ 企业版

3.19 版本中添加了对 to-many 路径(隐式显式)的支持。虽然显式 to-many 路径非常强大,但用户可能希望像隐式 to-one 路径一样方便地使用隐式 to-many 路径。然而,jOOQ 不像其他 ORM 那样开箱即用地支持这些路径,并且用户在简单示例中可能会期望支持。例如,考虑以下“显而易见”的示例

// Get all authors and count their books
create.select(AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME, count(AUTHOR.book().ID))
      .from(AUTHOR)
      .groupBy(AUTHOR.ID)
      .fetch();

它读起来很流畅:“获取所有作者并计算他们的书籍数量”。预期生成的查询是

SELECT AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME, COUNT(BOOK.ID)
FROM AUTHOR
LEFT JOIN BOOK ON BOOK.AUTHOR_ID = AUTHOR.ID
GROUP BY AUTHOR.ID

另一个很酷的例子是这个巧妙的 ANTI JOIN

// Get all authors without any book
create.select(AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME)
      .from(AUTHOR)
      .where(AUTHOR.book().ID.isNull())
      .fetch();
SELECT AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME
FROM AUTHOR
LEFT JOIN BOOK
  ON BOOK.AUTHOR_ID = AUTHOR.ID
WHERE BOOK.ID IS NULL

但是,如果以上情况是可能的,那么以下反例将产生非常令人惊讶的结果!人们会期望 ANTI JOIN 的反向操作会产生一个 SEMI JOIN,但这不会发生

// Get all authors with books
create.select(AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME)
      .from(AUTHOR)
      .where(AUTHOR.book().ID.isNotNull())
      .fetch();
SELECT AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME
FROM AUTHOR
LEFT JOIN BOOK
  ON BOOK.AUTHOR_ID = AUTHOR.ID
WHERE BOOK.ID IS NOT NULL

现在,我们得到了重复的作者。每个作者的书籍都有一份,这是由于 LEFT JOIN 创建的笛卡尔积。

偶然的重复对象并不是这种隐式 to-many 路径 JOIN 会导致的主要问题。主要问题是,放置在 SELECT 子句WHERE 子句(和其他子句)中的隐式 to-many 路径将能够生成行,而实际上 SELECT 仅转换行(如 Stream.map()),WHERE 仅过滤行(如 Stream.filter())。这些子句能够有效地生成行将是非常不符合 SQL 习惯的,并且令人困惑。

为了防止这种情况,当遇到隐式 to-many JOIN 路径表达式时,默认行为是抛出一个异常。

为了防止这种情况,用户有两种选择

  • 使用 Settings.renderImplicitJoinToManyType 覆盖默认设置。这适用于所有查询,并为知道自己在做什么的高级用户删除了上述标量子查询保护。
  • 使用 显式路径 JOIN 来指定确实需要 LEFT JOIN(或任何其他类型的 JOIN),请参见以下示例
// Get all authors with books
create.select(AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME)
      .from(AUTHOR)
      .leftJoin(AUTHOR.book()) // Now, the LEFT JOIN is explicit and cartesian products aren't accidental.
      .where(AUTHOR.book().ID.isNotNull())
      .fetch();

反馈

您对此页面有任何反馈吗? 我们很乐意听到!

The jOOQ Logo