Jakarta Persistence 3.2 简介

1、简介

Jakarta Persistence(前身为 JPA)是 Java 中对象关系映射的标准 API。它使开发人员能够在 Java 应用中管理关系数据,并通过使用注解和实体类将 Java 对象映射到数据库表来简化数据库交互。

本文将带你了解 Jakarta Persistence 3.2 中引入的一些关键新功能,重点介绍在配置、性能和可用性方面的改进。

2、Jakarta Persistence 3.2 是什么?

Jakarta Persistence 3.2 是 Jakarta Persistence API 的最新版本,它为 Java 应用中的对象关系映射(ORM)提供了一种标准化方法。

该版本改进了查询功能、性能和可用性,并增强了对现代数据库功能的支持。

要添加对 Jakarta Persistence 3.2 的支持,必须在 pom.xml 中添加以下 Maven 依赖

<dependency>
    <groupId>jakarta.persistence</groupId>
    <artifactId>jakarta.persistence-api</artifactId>
    <version>3.2.0</version>
</dependency>

此外,还需要支持该 API 的最新 Hibernate 7 版本:

<dependency>
    <groupId>org.hibernate.orm</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>7.0.0.Beta1</version>
</dependency>

3、关键的新功能

Jakarta Persistence 3.2 引入了一些新功能,以改进数据库连接处理、模式配置和事务管理。

3.1、持久化配置

最新的 Jakarta Persistence 3.2 版本添加了编程式 API,可使用 PersistenceConfiguration 类而不是传统的 persistence.xml 文件获取 EntityManagerFactory 接口的实例,这提供了灵活性,尤其是在运行时配置可能不同的环境中。

创建一个包含 idfullNamedepartment 等字段的 Employee 实体类来演示新功能和增强功能:

@Entity
public class Employee {
    @Id
    private Long id;

    private String fullName;

    private String department;

    // Getter / Setter 方法省略
}

如上,@Entity 注解表明 Employee 类是一个持久实体,而 @Id 注解则将 id 字段标记为主键。

现在,使用新引入的 PersistenceConfiguration 类以编程方式配置 EntityManagerFactory 类的实例:

EntityManagerFactory emf = new PersistenceConfiguration("EmployeeData")
  .jtaDataSource("java:comp/env/jdbc/EmployeeData")  // 数据源
  .managedClass(Employee.class)     // 注册实体类
  .property(PersistenceConfiguration.LOCK_TIMEOUT, 5000) // 锁超时
  .createEntityManagerFactory();

assertNotNull(emf);

如上,通过设置数据源、注册实体类和配置锁超时等属性来创建 EntityManagerFactory 的实例。

3.2、Schema 管理 API

新版 Jakarta Persistence 还引入了 Schema Manager API,允许开发人员以编程方式管理 Schema。这简化了开发和生产环境中的数据库迁移和 Schema 验证。

例如,我们现在可以使用 API 创建 Schema:

emf.getSchemaManager().create(true);

总共有四个可用于 Schema 管理的方法:

  • create():在持久化单元(Persistence Unit)中创建与实体相关的表。
  • drop():删除与持久化单元中的实体相关联的表。
  • validate():根据实体映射验证 Schema。
  • truncate():清除实体相关表中的数据。

3.3、运行事务和调用事务

现在有了运行事务(runInTransaction)和调用事务(callInTransaction)等新方法,通过为应用程序管理的 EntityManager 提供活动事务来改进数据库事务的处理。

通过这些方法,我们可以在事务范围内执行操作,并在必要时访问底层数据库连接。

我们可以使用这些方法在事务中运行查询,并直接对数据库连接进行操作:

emf.runInTransaction(em -> em.runWithConnection(connection -> {
    try (var stmt = ((Connection) connection).createStatement()) {
        stmt.execute(
          "INSERT INTO employee (id, fullName, department) VALUES (8, 'Jane Smith', 'HR')"
        );
    } catch (Exception e) {
        Assertions.fail("JDBC operation failed");
    }
}));

var employee = emf.callInTransaction(em -> em.find(Employee.class, 8L));

assertNotNull(employee);
assertEquals("Jane Smith", employee.getFullName());

首先,我们使用 runInTransaction() 执行 SQL,在事务中向数据库插入一名新员工(employee)。然后,callInTransaction() 方法会检索并验证插入的员工详细信息。

3.4、TypedQueryReference 接口

在 Jakarta Persistence 中,命名查询(Named Query)通常是通过字符串引用的,因此容易出错,如查询名称中的错别字。

新引入的 TypedQueryReference 接口旨在通过将命名查询链接到静态元模型(static metamodel)来解决这个问题,从而使它们在编译时是类型安全和可发现的。

用命名查询更新 Employee 实体,使用 department 字段进行检索:

@Entity
@NamedQuery(
  name = "Employee.byDepartment",
  query = "FROM Employee WHERE department = :department",
  resultClass = Employee.class
)
public class Employee {
// ...
}

编译后,相应的静态元模型(StaticMetamodel)将按如下方式生成:

@StaticMetamodel(Employee.class)
@Generated("org.hibernate.processor.HibernateProcessor")
public abstract class Employee_ {
    public static final String QUERY_EMPLOYEE_BY_DEPARTMENT = "Employee.byDepartment";
    public static final String FULL_NAME = "fullName";
    public static final String ID = "id";
    public static final String DEPARTMENT = "department";

    // ...
}

现在,我们可以使用 QUERY_EMPLOYEE_BY_DEPARTMENT 常量来引用 Employee 实体上定义的命名查询 byDepartment

Map<String, TypedQueryReference> namedQueries = emf.getNamedQueries(Employee.class);

List employees = em.createQuery(namedQueries.get(QUERY_EMPLOYEE_BY_DEPARTMENT))
  .setParameter("department", "Science")
  .getResultList();

assertEquals(1, employees.size());

如上,我们可以看到 EntityManagerFactorygetNamedQueries() 方法会返回一个命名查询及其 TypedQueryReferenceMap。然后,我们使用 EntityManagercreateQuery() 方法获取 Science 部门的员工,并断言该列表恰好包含一个结果,以确认查询的预期输出。

因此,TypedQueryReference 接口可确保命名查询存在并被正确引用,从而提供编译时验证。

3.5、EntityGraph 中的类型安全

Jakarta Persistence 的实体图(entity graph)允许在执行查询时立即加载属性。

现在,有了新版本的 Jakarta Persistence,它们也是类型安全的了 - 确保图中引用的属性在编译时是有效和存在的,从而降低了出错的风险。

例如,使用静态元模型 Employee_ 来确保编译时的类型安全:

var employeeGraph = emf.callInTransaction(em -> em.createEntityGraph(Employee.class));
employeeGraph.addAttributeNode(Employee_.department);

var employee = emf.callInTransaction(em -> em.find(employeeGraph, 7L));

assertNotNull(employee);
assertEquals("Engineering", employee.getDepartment());

如上,我们从静态元模型类访问 department 属性,该类会验证该属性是否存在于 Employee 类中,否则,如果我们弄错了属性,就会产生编译错误。

4、可用性的提升

Jakarta Persistence 3.2 引入了多项性能和可用性增强功能,以简化数据库查询并提高整体应用程序性能。

4.1、简化的 JPQL

JPQL 现在支持这种精简的查询语法,Jakarta 数据查询语言(JPQL 的一个子集)也常用这种语法。

例如,当一个实体没有指定别名时,它会自动默认为相关的表:

Employee employee = emf.callInTransaction(em -> 
  em.createQuery("from Employee where fullName = 'Tony Blair'", Employee.class).getSingleResult()
);

assertNotNull(employee);

如上,我们没有为 Employee 实体指定别名。相反,别名的默认值是 this,这样我们就可以直接对实体执行操作,而无需限定字段名称。

4.2、cast() 方法

Jakarta Persistence 中新增的 cast() 方法允许我们对查询结果进行投影:

emf.runInTransaction(em -> em.persist(new Employee(11L, "123456", "Art")));

TypedQuery<Integer> query = em.createQuery(
  "select cast(e.fullName as integer) from Employee e where e.id = 11", Integer.class
);
Integer result = query.getSingleResult();

assertEquals(123456, result);

在上例中,我们首先插入一条新的 Employee 记录,fullName 值为 123456。然后,使用 JPQL 查询将 String 属性 fullName 转换为 Integer

4.3、left()right() 方法

JPQL 还允许使用 left() 等字符串操作方法,使用索引值提取子串:

TypedQuery<String> query = em.createQuery(
  "select left(e.fullName, 3) from Employee e where e.id = 2", String.class
);
String result = query.getSingleResult();

assertEquals("Tom", result);

如上,我们使用 JPQL 函数 left()fullName 左边截取了子串 Tom

同样,它还提供了用于截取子串的 right() 方法:

query = em.createQuery("select right(e.fullName, 6) from Employee e where e.id = 2", String.class);
result = query.getSingleResult();

assertEquals("Riddle", result);

如上,我们从 fullName 的右侧截取了子串 Riddle

4.4、replace() 方法

同样,JPQL 中也有 replace() 函数,允许我们替换 String 的一部分:

TypedQuery<String> query = em.createQuery(
  "select replace(e.fullName, 'Jade', 'Jane') from Employee e where e.id = 4", String.class
);
String result = query.getSingleResult();

assertEquals("Jane Gringer", result);

如上,replace() 函数将 fullName 属性中的 Jade 替换为新的字符串值 Jane

4.5、id() 方法

新的 id() 方法可让我们提取数据库记录的 ID:

TypedQuery<Long> query = em.createQuery(
  "select id(e) from Employee e where e.fullName = 'John Smith'", Long.class
);
Long result = query.getSingleResult();

assertEquals(1L, result);

id() 函数获取 fullNameJohn SmithEmployee 记录的主键。

4.6、改进排序功能

最后,Jakarta Persistence 3.2 的排序改进增加了 null 值优先和大小写不敏感排序功能,可使用标量表达式(如 lower()upper())进行排序:

emf.runInTransaction(em -> {
    em.persist(new Employee(21L, "alice", "HR"));
    em.persist(new Employee(22L, "Bob", "Engineering"));
    em.persist(new Employee(23L, null, "Finance"));
    em.persist(new Employee(24L, "charlie", "HR"));
});

TypedQuery<Employee> query = em.createQuery(
  "SELECT e FROM Employee e ORDER BY lower(e.fullName) ASC NULLS FIRST, e.id DESC", Employee.class
);

List<Employee> sortedEmployees = query.getResultList();

assertNull(sortedEmployees.get(0).getFullName());
assertEquals("alice", sortedEmployees.get(1).getFullName());
assertEquals("Bob", sortedEmployees.get(2).getFullName());
assertEquals("charlie", sortedEmployees.get(3).getFullName());

如上,我们按 fullName 字段以大小写不敏感的升序对 Employee 记录进行了排序(使用 lower() 函数),null 值优先,然后按 id 降序。

5、总结

本文介绍了最新的 Jakarta Persistence 3.2,其中包含大量新功能和改进,可简化 ORM 操作和高效数据处理。


Ref:https://www.baeldung.com/jakarta-persistence-3-2