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
接口的实例,这提供了灵活性,尤其是在运行时配置可能不同的环境中。
创建一个包含 id
、fullName
和 department
等字段的 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());
如上,我们可以看到 EntityManagerFactory
的 getNamedQueries()
方法会返回一个命名查询及其 TypedQueryReference
的 Map
。然后,我们使用 EntityManager
的 createQuery()
方法获取 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()
函数获取 fullName
为 John Smith
的 Employee
记录的主键。
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