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. 持久化配置

最新版 新增程序化 API,通过 PersistenceConfiguration 类获取 EntityManagerFactory 实例,替代传统的 persistence.xml 文件——这在运行时配置多变的环境中提供了更大灵活性。

为演示新特性,创建包含 idfullNamedepartment 字段的 Employee 实体类:

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

    private String fullName;

    private String department;

    // getters and setters ...
}

这里 @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. 模式管理器 API

新版本引入模式管理器 API,允许开发者程序化管理数据库模式。这简化了开发和生产环境中的数据库迁移与模式验证。

例如,可通过 API 启用模式创建:

emf.getSchemaManager().create(true);

共提供四个模式管理函数:

  • ✅ *create()*:创建持久化单元关联的表
  • ✅ *drop()*:删除持久化单元关联的表
  • ✅ *validate()*:验证模式与实体映射是否匹配
  • ✅ *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() 在事务内插入新员工记录,再用 callInTransaction() 检索并验证插入的员工详情。

3.4. TypedQueryReference 接口

命名查询在 Jakarta Persistence 中通常通过字符串引用,易因查询名称拼写错误导致问题。

新增的 TypedQueryReference 接口通过将命名查询链接到静态元模型解决此问题,实现编译时类型安全检查

更新 Employee 实体,添加按 department 字段搜索的命名查询:

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

编译后生成对应的静态元模型:

@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() 方法返回命名查询及其 TypedQueryReference 的映射。随后使用 EntityManagercreateQuery() 方法获取 "Science" 部门的员工,断言结果列表仅包含一条记录。

TypedQueryReference 接口确保命名查询存在且被正确引用,提供编译时验证。

3.5. EntityGraph 的类型安全

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 Data Query Language(JPQL 的子集)中常用。

当实体未指定别名时,自动默认为关联表名:

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

assertNotNull(employee);

此处未为 Employee 实体指定别名,别名默认为 this,可直接在实体上操作而无需限定字段名。

4.2. cast() 函数

新增 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);

先插入 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() 函数,可替换字符串部分内容:

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() 方法可提取数据库记录标识符:

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() 函数获取 fullName 为 "John Smith" 的员工记录的主键。

4.6. 改进的排序功能

排序增强支持 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 大小写不敏感升序排序(使用 lower() 函数),null 值优先,并按 id 降序排列员工记录。

5. 结论

本文介绍了最新的 Jakarta Persistence 3.2,它带来一系列新特性和改进,简化了 ORM 操作并提升数据处理效率

我们涵盖了简化持久化配置、程序化模式管理、事务管理等特性,并探讨了 JPQL 的可用性改进,包括新增函数以编写更优查询。这些改进让开发者能更高效地构建健壮的数据访问层。


原始标题:Introduction to Jakarta Persistence 3.2 | Baeldung