来自 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);
唉。你能想象我的尴尬。
我们不是穿上麻衣,而是后来才解决这个问题。新的标记接口FindOption
、LockOption
和RefreshOption
各有几个内置实现,但也可以实现来表示特定提供者的选项。
var book =
em.find(Book.class, isbn, CacheRetrieveMode.BYPASS,
Timeout.milliseconds(500), READ_ONLY);
这种方法更易读,并且类型安全得多。
出于同样的原因,我们还增加了
-
为
EntityTransaction
添加了setTimeout()
,以及 -
为
EntityManager
和Query
添加了setCacheStoreMode()
和setCacheRetrieveMode()
。
类型安全与静态元模型
静态元模型是我为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都支持union
、intersect
和except
,这些操作与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.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
没有显式指定生成器名称时,提供程序将自动选择与同一实体类或包中定义的相同名称的匹配的序列或表生成器。
与 CDI 和其他依赖注入容器的集成
现在 persistence.xml
文件具有 <qualifier>
和 <scope>
元素,支持使用 CDI 注入 EntityManager
或 EntityManagerFactory
。
实际上,这些元素不仅限于与 CDI 一起使用,还可以与任何 jakarta.inject
的实现一起使用。