§使用 JPA 访问您的数据库
重要说明
请注意,JPA Bean 验证目前在 Play 2.9 中不受支持。有关更多信息,请参阅以下详细信息.
§将依赖项添加到您的项目
首先,您需要告诉 Play 您的项目依赖于 javaJpa
,它将提供 JDBC 和 JPA api 依赖项。
Play 中没有内置的 JPA 实现;您可以选择任何可用的实现。例如,要使用 Hibernate,只需将以下依赖项添加到您的项目
libraryDependencies ++= Seq(
javaJpa,
"org.hibernate" % "hibernate-core" % "6.4.4.Final" // replace by your jpa implementation
)
§通过 JNDI 公开数据源
JPA 要求数据源可以通过 JNDI 访问。您可以通过在 conf/application.conf
中添加以下配置,将任何 Play 管理的数据源公开到 JNDI。
db.default.jndiName=DefaultDS
有关如何配置数据源的更多信息,请参阅 数据库文档。
§创建持久化单元
接下来,您需要创建一个合适的 persistence.xml
JPA 配置文件。将其放入 conf/META-INF
目录中,以便它被正确地添加到您的类路径中。
以下是一个与 Hibernate 一起使用的示例配置文件
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd"
version="3.0">
<persistence-unit name="defaultPersistenceUnit" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<non-jta-data-source>DefaultDS</non-jta-data-source>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
</properties>
</persistence-unit>
</persistence>
最后,您需要告诉 Play 您的 JPA 提供程序应该使用哪个持久化单元。这可以通过在您的 conf/application.conf
中设置 jpa.default
属性来完成。
jpa.default=defaultPersistenceUnit
§部署使用 JPA 的 Play
在使用 JPA 的情况下,在开发模式下运行 Play 可以正常工作,但为了部署应用程序,您需要将以下内容添加到您的 build.sbt
文件中。
PlayKeys.externalizeResourcesExcludes += baseDirectory.value / "conf" / "META-INF" / "persistence.xml"
注意:有关如何配置外部化资源的更多信息,请参阅 此处。
上述设置确保persistence.xml
文件始终保留在生成的应用程序jar
文件内部。
这是 JPA 规范 的要求。根据该规范,persistence.xml
文件必须位于其持久化单元的实体所在的同一个jar
文件中,否则这些实体将无法用于持久化单元。(但是,您可以通过<jar-file>xxx.jar</jar-file>
显式添加包含实体的jar
文件到持久化单元 - 但这在 Play 中效果不佳,因为它会在开发模式下出现FileNotFoundException
,因为在该模式下不会生成jar
文件。此外,这在生产模式下也不起作用,因为在部署应用程序时,生成的应用程序jar
文件的名称会随着每次新发布而更改,因为当前版本的应用程序会附加到它。)
§使用 play.db.jpa.JPAApi
Play 为您提供了一个方便的 API 来使用 实体管理器 和事务。此 API 由 play.db.jpa.JPAApi
定义,它可以在其他对象中注入,例如以下代码
import jakarta.persistence.*;
import java.util.concurrent.*;
import javax.inject.*;
import play.db.jpa.JPAApi;
@Singleton
public class JPARepository {
private JPAApi jpaApi;
private DatabaseExecutionContext executionContext;
@Inject
public JPARepository(JPAApi api, DatabaseExecutionContext executionContext) {
this.jpaApi = api;
this.executionContext = executionContext;
}
}
我们建议将您的 JPA 操作隔离在 存储库 或 DAO 后面,以便您可以使用自定义执行上下文和事务来管理所有 JPA 操作。
这意味着所有 JPA 操作都在接口后面完成 - JPA 类是包私有的,没有将持久化感知对象暴露给应用程序的其余部分,并且会话不会在定义异步边界的函数(即返回 CompletionStage
)之外保持打开状态。
这可能意味着您的域对象(在 DDD 术语中称为聚合根)包含对存储库的内部引用,并调用它来返回实体和值对象的列表,而不是保持会话打开并使用基于 JPA 的延迟加载。
§使用自定义执行上下文
注意:直接在 Action 中使用 JPA(使用 Play 的默认渲染线程池)将限制您异步使用 Play 的能力,因为 JDBC 会阻塞它正在运行的线程。
在使用 JPA 时,您应该始终使用自定义执行上下文,以确保 Play 的渲染线程池完全专注于渲染页面并充分利用核心。您可以使用 Play 的 CustomExecutionContext
类来配置一个专门用于服务 JDBC 操作的自定义执行上下文。有关更多详细信息,请参见 JavaAsync 和 ThreadPools。
Play 下载页面上的所有 Play 示例模板,这些模板使用阻塞 API(即 Anorm、JPA),都已更新为在适当的地方使用自定义执行上下文。例如,访问 https://github.com/playframework/play-samples/tree/3.0.x/play-java-jpa-example 显示 JPAPersonRepository 类接受一个 DatabaseExecutionContext
,它包装了所有数据库操作。
对于涉及 JDBC 连接池的线程池大小,您需要一个固定大小的线程池,该线程池的大小与连接池匹配,并使用线程池执行器。按照 HikariCP 的池大小页面 中的建议,您应该将 JDBC 连接池配置为物理核心数量的两倍,加上磁盘主轴的数量,即如果您有四个核心 CPU 和一个磁盘,则池中总共有 9 个 JDBC 连接。
# db connections = ((physical_core_count * 2) + effective_spindle_count)
fixedConnectionPool = 9
database.dispatcher {
executor = "thread-pool-executor"
throughput = 1
thread-pool-executor {
fixed-pool-size = ${fixedConnectionPool}
}
}
§运行 JPA 事务
JPAApi
为您提供了各种 withTransaction(...)
方法,用于在 JPA 事务中执行任意代码。但是,这些方法不包含自定义执行上下文,因此必须在具有 IO 绑定执行上下文的 CompletableFuture
中包装。
§示例
使用 JPAApi.withTransaction(Function<EntityManager, T>)
public CompletionStage<Long> runningWithTransaction() {
return CompletableFuture.supplyAsync(
() -> {
// lambda is an instance of Function<EntityManager, Long>
return jpaApi.withTransaction(
entityManager -> {
Query query = entityManager.createNativeQuery("select max(age) from people");
return (Long) query.getSingleResult();
});
},
executionContext);
}
使用 JPAApi.withTransaction(Consumer<EntityManager>)
来运行批处理更新
public CompletionStage<Void> runningWithRunnable() {
// lambda is an instance of Consumer<EntityManager>
return CompletableFuture.runAsync(
() -> {
jpaApi.withTransaction(
entityManager -> {
Query query =
entityManager.createNativeQuery("update people set active = 1 where age > 18");
query.executeUpdate();
});
},
executionContext);
}
§Play 2.9 中当前不支持 Bean 验证
JPA 规范定义了一种验证模式,可以启用实体验证。如果用户没有覆盖此模式(例如,在 `persistence.xml` 中使用 `
Play 和 Hibernate ORM 6+ 中存在挑战。在此版本中,Hibernate ORM 不再检测类路径上的 Hibernate Validator 6.x 版本(Play 使用),因为它继续使用 `javax.validation`。但是,Hibernate ORM 6+ 仅检测 `jakarta.validation` 类。
虽然 Hibernate Validator 7.x 已过渡到 Jakarta,但在 Play 中升级到该版本目前具有挑战性。Play 在 Play-Java-Forms 中使用库来帮助绑定,但只有这些库的较新版本采用了 Jakarta 并支持 Hibernate Validator 7。不幸的是,这些较新的库版本仅与 Java 17 兼容。由于 Play 2.9 继续支持 Java 11,因此升级不可行。尝试手动将 hibernate-validator 版本覆盖到 7.x 将导致 Play-Java-Form 功能出现故障。
如果 Bean Validation 的缺失对您的用例构成问题,请告知我们。这样,我们可以探索解决此挑战的潜在解决方案。
§启用 Play 数据库演变
阅读 Evolutions 了解 Play 数据库演进的用途,并按照设置说明进行操作。
下一步:使用缓存
发现本手册中的错误? 此页面的源代码可在 此处 找到。 阅读完 文档指南 后,请随时贡献拉取请求。 有问题或建议要分享? 前往 我们的社区论坛 与社区进行交流。