Hibernate ORM版本6.5已经收到了几个候选发布版本,最终发布版也将很快推出。本文重点介绍这个版本带来的一个改进:高效检索非标识符数据库生成值。

之前发生了什么

Hibernate ORM已经支持通过SQL的RETURNING子句或JDBC的Statement API的getGeneratedKeys()方法,在执行的insert语句中检索GenerationType.IDENTITY(例如,请参阅PostgreSQL文档中的此页面this page of PostgreSQL’s documentation)或数据库支持这两种功能中的任何一种。

实体映射可以有多个执行时的生成属性(即数据库端在执行或执行后填充的列,但不属于主键部分)。此外,动态值生成不仅限于插入操作,还可以在更新管理实体实例时发生。您的映射值可能依赖于数据库函数的执行,或者通过触发器在执行语句时动态更新的列。

Hibernate需要在将其存储在持久化上下文(即一级缓存)中时读取这些值,以确保与数据库状态的一致性。到目前为止,对于包含在执行时生成属性的映射,每次发出insertupdate语句后都会运行一个后续的select查询来检索其值并相应地填充相应的实体实例。

以这个简单的映射为例

@Entity
class TestEntity {
    @Id
    Long id;

    @Generated( event = EventType.INSERT )
    String name;

    @UpdateTimestamp( source = SourceType.DB )
    Date updateDate;
}

在这里,name属性可能由数据库触发器在插入时生成,而updateDate可以通过在执行语句时使用localtimestamp函数来填充。在持久化这种类型的实体时,Hibernate将需要运行2条语句

-- insert the row on the database
insert
into
    TestEntity
    (id, updateDate)
values
    (?, localtimestamp)

-- select back the generated values
select
    name,
    updateDate
from
    TestEntity

新内容

在Hibernate 6.5中,执行insertupdate语句时,也会高效地检索任意数据库生成的值,即通过insert …​ returning语法或已用于IDENTITYgetGeneratedKeys() API。这实际上将插入或更新包含生成值的托管实体所需的数据库往返次数减半,从而取代了在变异应用后运行额外的select查询的需求。

从用户的角度来看,这种改进是完全透明的:您无需更改任何实体映射或配置额外的属性;从现在起,Hibernate将自动为您更高效地处理生成值。

例如,在支持insert …​ returning语法的数据库上为前一个示例创建的插入语句现在可能看起来像这样

-- insert the row on the database and retrieve the values in one go
insert
into
    TestEntity
    (id, updateDate)
values
    (?, localtimestamp)
returning name, updateDate

涉及在执行时生成值的映射的变异操作的性能应得到大幅提高。请记住,Hibernate的语句批处理功能不能与生成值结合使用,因此以前必须为每个变异行发出2个不同的语句(变异本身和随后的选择)。

我们利用了在执行变异语句时从数据库检索任意属性的新功能,以便通过@RowId注解(@RowId)高效地检索类似行ID的值,然后像往常一样在CRUD操作中利用它们。

这项新功能可能不适用于所有数据库,特别是对于旧版本。Hibernate团队已测试并确定了每个官方支持的Dialect如何利用新功能,大多数现代数据库都支持某种形式的效率较高的生成值检索。

如果您想了解更多信息,请参阅Hibernate Jira上的原始改进提案

展望

多亏了这次改进,我们现在能够为insertupdate变异语句返回任意的执行时生成的值。这可能会为用户界面级功能打开大门,例如通过自定义HQL变异查询语法来指定一个自定义的属性列表,以运行更新后的结果。

如果您想让我们知道您对这个新功能有何看法,或者如果您对此有任何疑问,请通过常规渠道与我们联系。


返回顶部