来自 Substack 的转帖。

我职业生涯中最宝贵的经验之一是与 Sun 的 Linda DeMichiel、TopLink 的 Mike Keith、Sybase 的 Evan Ireland 等人一起设计和编写 Java Persistence 规范的第一个版本。

如今这项技术得到了广泛的认可,甚至包括之前的批评者。但近年来,尽管名称已改为 Jakarta Persistence,该规范的发展并不迅速。但现在已经不同了。在过去的一年左右的时间里,Oracle 的 Lukas Jungmann 和我一直在努力,为您带来长时间以来 Persistence 的最大版本。

本文将重点介绍我们添加到 Jakarta Persistence 中的新功能。值得一提的是,相当多的工作已经用于澄清现有功能的语义,并为了清晰性和可读性重写了规范的某些部分。这是一个持续的工作。该规范长度超过 500 页;在不改变其意义的情况下重写这样的文本是一个缓慢而费力的过程。

在之前的一篇文章中,我谈到了 Jakarta Data。这两个规范的协调一致是一个进一步的重点。

API 改进

本节有很多内容要介绍。让我们从一个新的启动 JPA 的方法开始。

程序性配置

使用 persistence.xml 配置持久化单元没有问题,但有时我们更喜欢直接编写 Java 代码。

var emf =
        new PersistenceConfiguration()
                .name("Bookshop")
                .nonJtaDataSource("java:global/jdbc/BookshopData")
                .managedClass(Book.class)
                .managedClass(Author.class)
                .property(PersistenceConfiguration.LOCK_TIMEOUT, 5000)
                .createEntityManagerFactory()

例如,LOCK_TIMEOUT 常量包含标准配置属性如 "jakarta.persistence.lock.timeout" 的名称。

现在我们有了 EntityManagerFactory,我们可能需要将模式导出到数据库。

程序性模式导出

新的 SchemaManager 接口与 Hibernate 6.2 中首次出现的类似名称的 API 具有同构性。

emf.getSchemaManager().create(true); // create all the tables and stuff

SchemaManager 甚至有可爱的 truncate() 方法,用于在测试前后进行清理。

emf.getSchemaManager().truncate(); // destroy all my data

接下来,我们需要获取一个会话,开始一个事务,处理可能发生的异常...

简化异常处理的便捷方法

现在EntityManagerFactory具有在事务中执行工作的操作,从而减轻了开发者编写杂乱的异常处理代码的需要。

emf.runInTransaction(em -> em.persist(book));

还有一个返回值的版本。

var book = emf.callInTransaction(em -> em.find(Book.class, isbn));

这些方法与Hibernate中的inTransaction()fromTransaction()非常相似。

偶尔,我们需要直接调用JDBC。因此,在EntityManager中为处理JDBC连接定义了类似的方法。

em.runWithConnection(connection -> {
    try (var procedure = connection.prepareCall("{call something(?)}")) {
        procedure.setLong(1, id);
        procedure.execute();
    }
});

我们现在已经到达了EntityManager本身。

选项!

在JPA 1.0中,我们决定让你向find()lock()refresh()方法传递“提示”——即充满字符串标签的值的映射。代码看起来像这样

var book =
        em.find(Book.class, isbn,
                Map.of("jakarta.persistence.cache.retrieveMode",
                            CacheRetrieveMode.BYPASS,
                       "jakarta.persistence.query.timeout", 500,
                       "org.hibernate.readOnly", true);

唉。你能想象我的尴尬。

我们不是穿上麻衣,而是后来才解决这个问题。新的标记接口FindOptionLockOptionRefreshOption各有几个内置实现,但也可以实现来表示特定提供者的选项。

var book =
        em.find(Book.class, isbn, CacheRetrieveMode.BYPASS,
                Timeout.milliseconds(500), READ_ONLY);

这种方法更易读,并且类型安全得多。

出于同样的原因,我们还增加了

  • EntityTransaction添加了setTimeout(),以及

  • EntityManagerQuery添加了setCacheStoreMode()setCacheRetrieveMode()

从分离引用获取托管引用

getReference()的这个重载版本首次出现在Hibernate 6.0中。现在它已经提升到EntityManager

<T> T getReference(T object);

此方法允许你用实体的分离引用(甚至未检索的代理)交换与持久化上下文关联的引用,而不需要检索任何数据。这很有用。

类型安全与静态元模型

静态元模型是我为JPA 2.0想出来的一个东西,作为一种使criteria查询API类型安全的方法。然后我花了一个多世纪怀疑这究竟是不是一个好主意。好吧,事实证明这确实是一个好主意,但criteria API并不是它的唯一应用,甚至不是最有用的应用。在Jakarta Persistence 3.2——以及Jakarta Data 1.0中——我们终于充分利用了它的全部潜力。

类型安全命名事项

静态元模型的新功能之一是它现在包含具有实体字段、命名查询、命名图和命名SQL结果集映射名称的static final常量。这个功能的一个许多用途是在定义双向关联映射。

@ManyToMany(mappedBy=Book_.AUTHORS)
List<Book> books;

这个功能已经在Hibernate 6中存在,我们已经开始看到社区接受它。但还有更多。

TypedQueryReference

TypedQueryReference接口代表对命名查询的类型化引用。EntityManager会与你交换这些中的一个以换取一个TypedQuery

TypedQueryReference<Book> bookNamedQuery = ... ;
TypedQuery<Book> query = em.createQuery(bookNamedQuery);

你肯定想知道,我们为什么要发明这样的东西?

嗯,静态元模型现在为你的每个命名查询都有一个这样的。

List<Book> books = em.createQuery(Book_.byTitle).getResultList();

没错,命名查询现在变得类型安全了。

EntityGraph

首次在JPA 2.1中引入的EntityGraph功能曾经有些混乱。(这次不能怪我,我没有为2.1做出贡献。)在Persistence 3.2中,我们清理了整个API,使其更易于使用。我们甚至不得不弃用某些错误类型的函数,这些函数将在4.0中删除。我们还将“fetch graph”与“load graph”的整个混淆区别区别开来。

当然,EntityGraph并没有被类型安全公交车落下。

var bookWithAuthors = em.createEntityGraph(Book.class);
bookWithAuthors.removeAttributeNode(Book_.publisher);
bookWithAuthors.addAttributeNode(Book_.authors);
var book = em.find(bookWithAuthors, isbn);

使用@NamedEntityGraph声明的图通过静态元模型可用。

var book = em.find(Book_.withAuthors, isbn);

不要对这个特性过于兴奋;@NamedEntityGraph注解本身仍然很难使用,因此最好还是在代码中指定实体图。

JPQL的增强

在本版本中,我们专注于添加一些Hibernate和EclipseLink已经支持的特性,这些特性是对JPQL规范扩展的支持。我们还进行了一些最后的修改,使JPQL与Jakarta Data的需求相一致。

单实体查询的简化语法

Hibernate早已允许您以以下简化形式编写查询

from Book where title like :pattern

请注意

  • Book没有别名,因此其字段不需要进行限定,并且

  • select子句是可选的,因为查询仅返回查询到的实体。

现在JPQL允许这样做,这是Jakarta Data查询语言(JPQL的一个子集)中编写查询的常规方式。

当实体没有显式指定别名时,其别名默认为此。

select count(this) from Book where title like :pattern

并集和交集

Hibernate和EclipseLink都支持unionintersectexcept,这些操作与SQL中这些操作的语义完全相同。这些操作现在是规范的一部分。

select name from Person
union select name from Organization

临时连接

现在允许使用实体类型之间的ANSI SQL风格临时连接。

from Author a join Customer c on a.name = c.firstName||' '||c.lastName

新的标准函数

持久化3.1已经添加了许多新的标准函数。在3.2中,我们还添加了cast()left()right()replace()id()version()

select cast(left(fileName,2) as Integer) as chapter from Document

我们最终还批准了使用标准SQL连接操作符||作为concat()的替代方案。

改进的排序

JPQL的order by子句非常有限,JPQL的实现支持的功能远远超过了规范“官方”要求的功能。我们现在批准使用

  • nulls firstnulls last来指定null值的优先级,以及

  • 使用任意标量表达式进行排序——特别是使用upper()lower()来实现不区分大小写的排序。

from Book order by lower(title) asc, publicationDate desc nulls first

映射注解的增强

持久化3.2在O/R映射领域没有添加任何重大新功能,但它有一些值得在此提及的细微之处。

枚举映射

全新的@EnumeratedValue注解允许您自定义Java枚举值与数据库中它们的编码之间的映射。

enum Status {
     OPEN(0), CLOSED(1), CANCELLED(-1);

     @EnumeratedValue
     final int intValue;

     Status(int intValue) {
         this.intValue = intValue;
     }
}

ID生成器

@SequenceGenerator@TableGenerator注解一直缺乏人体工程学。它们必须直接放在实体类上,或者放在其@Id字段上,但用户被迫声明生成器的名称,并在@GeneratedValue注解中引用它。这相当冗余(并且也缺乏类型安全)。我们现在

  • 使name成为可选的,并且

  • 允许这些注解出现在PACKAGE级别。

@GeneratedValue没有显式指定生成器名称时,提供程序将自动选择与同一实体类或包中定义的相同名称的匹配的序列或表生成器。

DDL生成改进

几个增强功能支持对DDL生成的改进控制

  • @Table@Column注解现在具有commentcheck成员,并且检查约束通过新的@CheckConstraint注解表示。

  • 许多注解新增了可用于将任意 SQL 片段附加到生成的 DDL 的 options 成员。对于 @Column,这现在替代了许多对有问题的 columnDefinition 成员的调用。

  • @Column 现在为映射时间戳提供了 secondPrecision

瞬间和年份

java.time 中的类型 InstantYear 现在被视为基本类型。

记录内嵌类型

Java 记录类型现在可以标注 @Embeddable 或用作 @IdClass

此外,我们还放宽了一些无用的限制

  • 实体和内嵌类型类现在可以是 static 内部类,并且

  • 主键类不再需要是 public 和可序列化的。

与 CDI 和其他依赖注入容器的集成

现在 persistence.xml 文件具有 <qualifier><scope> 元素,支持使用 CDI 注入 EntityManagerEntityManagerFactory

实际上,这些元素不仅限于与 CDI 一起使用,还可以与任何 jakarta.inject 的实现一起使用。

哦,你真的做到这一步了吗?

呼!这有很多新内容。虽然许多这些增强可能相当公正地被描述为“微小”,但我希望你能看到其中存在一个改进类型安全性的共同主题,并且它们合在一起代表了一个相当大的进步。

代表 Hibernate 团队发言,我们的 JPA 3.2 实现进展非常顺利,并将作为 Hibernate 7.0 在今年晚些时候发布。我保证你会喜欢的。

在未来的文章中,我将谈论我们对 Persistence 4.0 的计划。


返回顶部