Hibernate ORM版本6.1.0.Final几天前刚刚发布,但公告并没有深入细节。如果你想要了解更多关于这个最新版本的一些热门新特性,请继续阅读。

FROM子句中的子查询

人们期待已久的一个特性,也称为HHH-3356,终于得到了实现!到目前为止,使用HQL和Criteria查询仅限于实体和关联,对于大多数情况来说已经足够好。但总有一些痒点,因为SQL允许在from子句中放置子查询,而Hibernate ORM使用HQL却无法在from子句中建模动态子查询。你最好的办法是,给实体添加@Subselect注解,告诉Hibernate数据来自SQL子查询而不是表引用。但这既不灵活也不易于移植。JIRA问题上的投票很高,我本来也计划要完成这个工作,因为我想要添加对lateral连接的支持。

忘记你对ORM查询的固有印象,因为Hibernate ORM即将通过引入派生from子句节点概念来改变游戏规则。

除了常规的from节点,这些节点在from子句根(例如,from MyEntity e)或关联(例如,join e.association a)的情况下引用实体类型,或者在嵌入式或元素集合连接的情况下引用基本/嵌入式类型(例如,join e.elementCollection e),现在又有一个新的成员。

我们将这种新的from节点称为“派生的”,因此现在你也可以有一个派生根和派生连接。这个节点的类型是“动态的”,因为它基于其子查询的选择项,而不是Java类。

简而言之,派生子查询的每个选择项别名都成为动态类型的一部分模型。模型部分的类型是从子查询中相应的选择项表达式类型派生出来的。

如果您了解如何在SQL中操作这些内容,您会发现HQL中大部分操作都相同,只是没有隐式命名选择项。让我们考虑以下查询示例

select d.derivedId, d.derivedText
from (
  select
    p.id as derivedId,
        p.firstname || ' ' || p.lastname as derivedText
  from
    Person p
) d

变量d指的是一个派生根类型,该类型包含两个模型部分derivedIdderivedTextderivedId的类型与Person#id相同,但derivedText的类型将是String,因为这是concat函数的表达式类型。

考虑到这些子查询可以包含聚合函数,这表明这不仅仅是一种封装数据子集的华丽方式,实际上它使HQL能够执行新的查询类型。

如果您对聚合不感兴趣,那么您可能更倾向于lateral子查询?在from子句中的子查询通常可以独立执行,这意味着它们不允许引用外部查询的别名。为了能够引用外部查询的别名,必须将一个from子句子查询标记为lateral,这通常等同于同名的SQL概念或其变体,如cross apply

考虑以下示例,以获取每个人的前三个访问位置

select p.firstname, d.nr, d.loc.street, d.loc.zip
from Person p
left join lateral (
  select
    row_number() over() as nr,
        count(*) as visitCount,
    v.location as loc
  from
    p.visitedLocations v
  group by
    v.location
  order by
    visitCount desc
  limit 3
) d

在这个示例中,from子句中的子查询引用了在封闭查询中定义的p别名的visitedLocations集合。在SQL中,这会导致查询person_visited_locations表,并在子查询中添加一个where子句谓词,以匹配由p给出的“当前”人的行。

请注意,并非所有数据库都支持lateral子查询,并且对lateral子查询的模拟仅限于某些子查询形状。为了避免在生产中出现意外,我们特别建议您检查使用lateral的查询是否可以在您的目标数据库上执行。

如果您认为这类似于嵌套循环连接,那么您完全正确。您可以阅读数据库文档了解如何实现横向连接的详细信息,但据我所知,横向连接将强制执行特定的连接顺序和嵌套循环连接算法。不要为此感到害怕,只是要注意其含义 ;)

正如您可能已经注意到的,一个常规的from子句子查询非常类似于一个公共表表达式(CTE),与著名的SQL with子句相关。现在我们已经实现了对from子句子查询的支持,这将为模拟CTE提供基础,我想向您透露,我们将开始在下一个Hibernate ORM版本中着手实现with子句的支持。

如果您现在感到失望,因为您理解常规CTE不会为查询表带来任何新功能,那么请放心,我们不会仅仅支持常规CTE。除了支持recursive关键字外,Hibernate ORM还将尝试添加对不太为人所知的searchcycle子句的支持。

如果您想跟踪CTE支持进度,请访问HHH-15328

JDBC数组类型支持

在Hibernate ORM 6.1之前,对Java数组的支持主要限于byte[]/char[]用于二进制/文本DDL类型以及将*到多关联映射为T[] Java数组。其他类型的数组至今被视为“可序列化”的,其效果是将Java数组以标准的Java序列化方式序列化到一个二进制DDL类型。

使用二进制DDL类型的最大问题是,开发人员无法使用SQL和常用的数据库客户端“看到”或操作实际数据。为了使数据更易于访问,同时仍然将其紧密打包在单个列中,有必要利用更高级的DDL类型和编码技术。

从Hibernate ORM 6.1开始,除了之前提到的特殊数组类型(如byte[]等)之外,基本数组以及基本集合现在是第一类公民,可以被映射到arrayjsonxml DDL类型。实际上,默认映射已更改为根据实际数据库支持将所谓的“基本复数”类型映射到arrayjsonxml。使用这些DDL类型允许通过相应数据类型的功能访问或操作单个元素。

利用这种新映射很简单,只需在持久化模型中声明基本类型数组即可。

@Entity
public class MyEntity {
    // ...

    int[] favoriteNumbers;
}

请注意,您不仅限于Java数组或原始类型。您还可以使用扩展java.util.Collection的复数类型,如ListSet

@Entity
public class MyEntity {
    // ...

    Set<TopicTag> watchedTags;

    enum TopicTag { TOPIC1, TOPIC2 }
}

只要您不对字段使用@ElementCollection注解,它将被视为“基本复数”映射。

Hibernate ORM 6.2或6.3可能会引入HQL访问器/操作器函数,这将允许访问/修改单个元素。使用结构可访问的DDL类型表示数据的好处在于,这些函数可以被正确地模拟。因此,即使您的数据库不支持原生数组类型,也有很大的可能性它支持JSON或XML函数,这些函数可以在幕后使用,以提供跨所有主要数据库的相同体验。

展望

在6.1.0.Final实现中,对from子查询支持的某些限制将在6.1.1.Final中作为HHH-15330的一部分被取消。

由于我提到了6.2或6.3的潜在功能,我想谈谈我计划为6.2做的工作,即支持将JDBC复合/结构类型映射到可嵌入类型。如果您想加入讨论以塑造这个功能,请通过在GitHub讨论中发表评论来告诉我们您的想法。

就这样,我总结了这篇关于6.1热点功能的文章!尝试使用6.1,并告诉我们您喜欢什么或什么不起作用。任何反馈都受欢迎:)


返回顶部