文档

§使用 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 操作的自定义执行上下文。有关更多详细信息,请参见 JavaAsyncThreadPools

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` 中使用 `... ` 或通过属性),它将默认设置为 `AUTO`。这意味着 Hibernate 会自动尝试检测类路径上是否存在 Bean Validation 参考实现(例如 Hibernate Validator),如果找到,则会自动激活该 Bean Validation。如果未检测到任何实现,则不会进行验证。但是,如果模式被手动设置为除 `NONE` 之外的其他值,则会抛出异常。

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 数据库演进的用途,并按照设置说明进行操作。

下一步:使用缓存


发现本手册中的错误? 此页面的源代码可在 此处 找到。 阅读完 文档指南 后,请随时贡献拉取请求。 有问题或建议要分享? 前往 我们的社区论坛 与社区进行交流。