Using Hibernate ORM and Jakarta Persistence

Solution

我们建议您遵循接下来的部分中的说明,按部就班地创建应用程序。然而,您可以直接跳到完成的示例。

克隆 Git 存储库: git clone $${quickstarts-base-url}.git,或下载 $${quickstarts-base-url}/archive/main.zip[存档]。

该解决方案位于 hibernate-orm-quickstart directory 中。

Setting up and configuring Hibernate ORM

在 Quarkus 中使用 Hibernate ORM 时,您无需拥有 persistence.xml 资源来对其进行配置。

使用这种经典的配置文件是一种选择,但是如果没有具体的高级需求,这是不需要的;因此,我们将首先了解如何在没有 persistence.xml 资源的情况下配置 Hibernate ORM。

在 Quarkus 中,您只需:

  • application.properties 中添加您的配置设置

  • 按惯例使用 @Entity 和任何其他映射注解来注释您的实体

自动执行其他配置需求:Quarkus 将做出一些主观的选项和有根据的猜测。

添加以下依赖关系到您的项目:

例如:

pom.xml
<!-- Hibernate ORM specific dependencies -->
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-hibernate-orm</artifactId>
</dependency>

<!-- JDBC driver dependencies -->
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-jdbc-postgresql</artifactId>
</dependency>
build.gradle
// Hibernate ORM specific dependencies
implementation("io.quarkus:quarkus-hibernate-orm")

// JDBC driver dependencies
implementation("io.quarkus:quarkus-jdbc-postgresql")

使用 @Entity 注释您的持久性对象,然后在 application.properties 中添加相关的配置属性。

Example application.properties
quarkus.datasource.db-kind = postgresql 1
quarkus.datasource.username = hibernate
quarkus.datasource.password = hibernate
quarkus.datasource.jdbc.url = jdbc:postgresql://localhost:5432/hibernate_db

quarkus.hibernate-orm.database.generation=drop-and-create 2
1 Configure the datasource.
2 在启动时删除并创建数据库(使用 update 仅更新架构)。

请注意,这些配置属性与典型的 Hibernate ORM 配置文件中的属性不同。它们通常映射到 Hibernate ORM 配置属性,但可能具有不同的名称,并且不一定彼此一一映射。

此外,Quarkus 将自动设置许多 Hibernate ORM 配置设置,并且通常会使用更现代的默认设置。

有关可以在 application.properties 中设置的项目的列表,请参阅 Hibernate ORM configuration properties

只要 Hibernate ORM 扩展列在您的项目依赖项中,就会基于 Quarkus datasource 配置创建 EntityManagerFactory

方言将根据您的数据源自动选择和配置;您可能需要 configure it to more precisely match your database

您便可以快乐地注入您的 EntityManager

Example application bean using Hibernate
@ApplicationScoped
public class SantaClausService {
    @Inject
    EntityManager em; 1

    @Transactional 2
    public void createGift(String giftDescription) {
        Gift gift = new Gift();
        gift.setName(giftDescription);
        em.persist(gift);
    }
}
1 注入您的实体管理器并尽情享受
2 将 CDIB bean 方法标记为 @Transactional,`EntityManager`将在提交时加入并刷新。
Example Entity
@Entity
public class Gift {
    private Long id;
    private String name;

    @Id
    @SequenceGenerator(name = "giftSeq", sequenceName = "gift_id_seq", allocationSize = 1, initialValue = 1)
    @GeneratedValue(generator = "giftSeq")
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

如果要在 Hibernate ORM 启动时加载 SQL 语句,请向 `resources`目录的根目录中添加一个 `import.sql`文件。此脚本可以包含任何 SQL DML 语句。务必让每条语句以分号结束。

这可用于为测试或演示准备好数据集。

务必在事务中封装修改数据库的方法(例如 entity.persist())。将 CDIB bean 方法标记为 `@Transactional`将为您执行该操作并将该方法设为事务边界。我们建议在您的应用程序入口点边界(例如您的 REST 端点控制器)中执行此操作。

Dialect

Supported databases

对于 supported databases,不必明确设置 Hibernate ORM dialect:它会根据数据源自动选择。

默认情况下,方言被配置为针对数据库的最低支持版本。

为了让 Hibernate ORM 生成更高效的 SQL,以避免解决方法并充分利用更多数据库功能,您可以明确设置数据库版本:

application.properties with an explicit db-version
quarkus.datasource.db-kind = postgresql
quarkus.datasource.db-version = 14.0 1
quarkus.datasource.username = hibernate
quarkus.datasource.password = hibernate
quarkus.datasource.jdbc.url = jdbc:postgresql://localhost:5432/hibernate_db
1 设置数据库版本。Hibernate ORM 方言将针对该版本。

一般来说,此处设置的版本应尽可能高,但必须低于或等于您的应用程序将连接到的任何数据库的版本。

如上所述,版本可以通过 `quarkus.datasource.db-version`配置属性预先明确配置,或者由 Quarkus 构建过程隐式设置为数据库的最低支持版本。Quarkus 尝试在启动时将此预先配置的版本与实际数据库版本进行比较,当实际版本较低时导致启动失败。 这是因为 Hibernate ORM 可能会生成对于比配置版本旧的数据库不可用的 SQL,这将导致运行时异常。 如果无法访问数据库,将记录警告,但启动将继续。

Other databases

如果 your database does not have a corresponding Quarkus extension,或如果出于某种原因,默认值不满足您的需求,您将需要明确设置 Hibernate ORM dialect

application.properties with an explicit dialect
quarkus.datasource.db-kind = postgresql
quarkus.datasource.username = hibernate
quarkus.datasource.password = hibernate
quarkus.datasource.jdbc.url = jdbc:postgresql://localhost:26257/hibernate_db

quarkus.hibernate-orm.dialect=Cockroach 1
1 设置 Hibernate ORM 方言。对于内置方言,预期值是 official list of dialects中的一个名称,*without*以 Dialect`后缀表示,例如 `Cockroach`以表示 `CockroachDialect。 对于第三方方言,预期值是全限定类名,例如 com.acme.hibernate.AcmeDbDialect

在这种情况下,请记住 JDBC 驱动程序或 Hibernate ORM 方言可能无法在 GraalVM 原生可执行文件中正常工作。

supported databases 一样,您可以显式配置数据库版本,以充分利用 Hibernate ORM:

application.properties with an explicit dialect and db-version
quarkus.datasource.db-kind = postgresql
quarkus.datasource.db-version = 22.2 1
quarkus.datasource.username = hibernate
quarkus.datasource.password = hibernate
quarkus.datasource.jdbc.url = jdbc:postgresql://localhost:26257/hibernate_db

quarkus.hibernate-orm.dialect=Cockroach 2
1 设置数据库版本。Hibernate ORM 方言将针对该版本。由于我们此处是针对 CockroachDB,因此我们传递的是 CockroachDB 版本,而不是 PostgreSQL 版本。
2 设置 Hibernate ORM 方言。

Varying database

在启用 database multi-tenancy 时,Hibernate ORM 将在运行时针对同一持久化单元使用多个数据源,并且 Quarkus 默认情况下无法分辨将使用哪个数据源,因此它将无法检测到在 Hibernate ORM 中要使用的方言。

因此,在启用 database multi-tenancy 时,建议显式将 Hibernate ORM 配置指向在运行时要使用的那些数据源中的一个数据源,例如使用 quarkus.hibernate-orm.datasource=base(base 是数据源的名称)。

这样做时,Quarkus 将从该数据源中推断出数据库版本和(如果可能)方言。对于不支持的数据库,您可能仍需要显式设置 Hibernate ORM 方言,如 this section 中所述。

Hibernate ORM configuration properties

有各种可选属性可用于优化 EntityManagerFactory 或指导 Quarkus 的猜测。

只要配置了默认数据源,就不需要任何必需的属性。

当未设置任何属性时,Quarkus 通常可以推断出它需要设置 Hibernate ORM 的所有内容,并且会让它使用默认数据源。

此处列出的配置属性允许您覆盖这些默认值,并自定义和调整各个方面。

Unresolved include directive in modules/ROOT/pages/hibernate-orm.adoc - include::../../../target/quarkus-generated-doc/config/quarkus-hibernate-orm.adoc[]

请勿在 application.properties 中混合 <<`persistence.xml`,persistence-xml>> 和 quarkus.hibernate-orm.* 属性。Quarkus 将引发异常。对要使用的方法下定决心。 如果您的类路径包含要忽略的 persistence.xml,请设置以下配置属性:

quarkus.hibernate-orm.persistence-xml.ignore=true

想在 Docker 中启动 PostgreSQL 服务器吗?

docker run --rm=true --name postgres-quarkus-hibernate -e POSTGRES_USER=hibernate \
           -e POSTGRES_PASSWORD=hibernate -e POSTGRES_DB=hibernate_db \
           -p 5432:5432 postgres:14.1

这将启动一个非持久性的空数据库:非常适合快速实验!

Multiple persistence units

Setting up multiple persistence units

可以使用 Quarkus 配置属性定义多个持久性单元。

quarkus.hibernate-orm. 命名空间根部的属性定义了默认持久性单元。例如,以下代码段定义了默认数据源和默认持久性单元:

quarkus.datasource.db-kind=h2
quarkus.datasource.jdbc.url=jdbc:h2:mem:default;DB_CLOSE_DELAY=-1

quarkus.hibernate-orm.database.generation=drop-and-create

使用基于映射的方法,可以定义命名的持久性单元:

quarkus.datasource."users".db-kind=h2 1
quarkus.datasource."users".jdbc.url=jdbc:h2:mem:users;DB_CLOSE_DELAY=-1

quarkus.datasource."inventory".db-kind=h2 2
quarkus.datasource."inventory".jdbc.url=jdbc:h2:mem:inventory;DB_CLOSE_DELAY=-1

quarkus.hibernate-orm."users".database.generation=drop-and-create 3
quarkus.hibernate-orm."users".datasource=users 4
quarkus.hibernate-orm."users".packages=org.acme.model.user 5

quarkus.hibernate-orm."inventory".database.generation=drop-and-create 6
quarkus.hibernate-orm."inventory".datasource=inventory
quarkus.hibernate-orm."inventory".packages=org.acme.model.inventory
1 定义一个名为 users 的数据源。
2 定义一个名为 inventory 的数据源。
3 定义一个名为 users 的持久性单元。
4 定义持久性单元使用的的数据源。
5 此配置属性很重要,但我们稍后会讨论。
6 定义一个名为 inventory 的持久性单元,该单元指向 inventory 数据源。

您可以混合使用默认数据源和具名数据源,或只使用其中一个。

默认持久性单元在默认情况下指向默认数据源。对于名为持久性单元,datasource 属性是必需的。您可以通过将其设置 <default>(这是默认数据源的内部名称)将持久性单元指向默认数据源。 完全可以使多个持久性单元指向同一个数据源。

Attaching model classes to persistence units

有两种方式可以将模型类附加到持久性单元,并且不应混用这两种方式:

  • 通过 packages 配置属性;

  • 通过 @io.quarkus.hibernate.orm.PersistenceUnit 包级别注释。

如果两者混合,则将忽略注释,仅考虑 packages 配置属性。

使用 packages 配置属性很简单:

quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.packages=org.acme.model.defaultpu

quarkus.hibernate-orm."users".database.generation=drop-and-create
quarkus.hibernate-orm."users".datasource=users
quarkus.hibernate-orm."users".packages=org.acme.model.user

此配置代码段将创建两个持久性单元:

  • 包含 org.acme.model.defaultpu 包下所有模型类(包括子包)的默认持久性单元。

  • 其命名为 users 持久性单元,其中将包含 org.acme.model.user 包及其子包下的所有模型类。

你可以将多个包附加到持久性单元:

quarkus.hibernate-orm."users".packages=org.acme.model.shared,org.acme.model.user

org.acme.model.sharedorg.acme.model.user 包下的所有模型类都将附加到 users 持久性单元。

还支持将给定的模型类附加到多个持久性单元。

模型类需要按照既定顺序添加到给定的持久性单元。也就是说,给定实体的所有依赖模型类(映射超类、嵌入式…​)都必须附加到持久性单元。由于我们在包级别处理持久性单元,所以它应该是简单的。

Panache 实体可以附加到仅一个持久性单元。 对于附加到多个持久性单元的实体,你无法使用 Panache。但你可以将这两种方法混合使用,并混合使用需要多个持久性单元的 Panache 实体和传统实体。 如果你有此用例并且有关于如何在不混淆简化 Panache 方法的情况下实现它的巧妙想法,请联系 quarkus-dev mailing list 上的我们。

连接持久性单元和模型类的第二种方法是使用包级别的 @io.quarkus.hibernate.orm.PersistenceUnit 注释。再次强调,这两种方法不能混用。

若要使用 packages 配置属性获取类似上述内容的配置,请使用以下内容创建 package-info.java 文件:

@PersistenceUnit("users") 1
package org.acme.model.user;

import io.quarkus.hibernate.orm.PersistenceUnit;
1 请注意,使用 @io.quarkus.hibernate.orm.PersistenceUnit 注释,而不是 Jakarta Persistence 注释。

我们仅支持使用 @PersistenceUnit 注释在包级别为模型类定义 @PersistenceUnit,而暂不支持在类级别使用。

请注意,与使用配置属性类似,我们考虑了注释的包及其所有子包。

CDI integration

如果您熟悉在 Quarkus 中使用 Hibernate ORM,则可能已经通过 CDI 注入 EntityManager

@Inject
EntityManager entityManager;

此操作将注入默认持久性单元的 EntityManager

注入已命名持久性单元的 EntityManager(在我们的示例中为 users)非常简单:

@Inject
@PersistenceUnit("users") 1
EntityManager entityManager;
1 这里,我们再次使用相同的 @io.quarkus.hibernate.orm.PersistenceUnit 注释。

您可以使用完全相同的机制注入已命名持久性单元的 EntityManagerFactory

@Inject
@PersistenceUnit("users")
EntityManagerFactory entityManagerFactory;

Activate/deactivate persistence units

如果在构建时配置了持久性单元,则默认情况下它在运行时处于活动状态,即 Quarkus 将在应用程序启动时启动相应的 Hibernate ORM SessionFactory

若要在运行时停用持久性单元,请将 quarkus.hibernate-orm[.optional name].active 设置为 false。然后,Quarkus 将不会在应用程序启动时启动相应的 Hibernate ORM SessionFactory。尝试在运行时使用相应的持久性单元的操作会失败,并显示明确的错误消息。

这在您希望应用程序能够运行时重新连接时特别有用。

例如,使用以下配置:

quarkus.hibernate-orm."pg".packages=org.acme.model.shared
quarkus.hibernate-orm."pg".datasource=pg
quarkus.hibernate-orm."pg".database.generation=drop-and-create
quarkus.hibernate-orm."pg".active=false
quarkus.datasource."pg".db-kind=h2
quarkus.datasource."pg".active=false
quarkus.datasource."pg".jdbc.url=jdbc:postgresql:///your_database

quarkus.hibernate-orm."oracle".packages=org.acme.model.shared
quarkus.hibernate-orm."oracle".datasource=oracle
quarkus.hibernate-orm."oracle".database.generation=drop-and-create
quarkus.hibernate-orm."oracle".active=false
quarkus.datasource."oracle".db-kind=oracle
quarkus.datasource."oracle".active=false
quarkus.datasource."oracle".jdbc.url=jdbc:oracle:///your_database

运行时的 Setting quarkus.hibernate-orm."pg".active=truequarkus.datasource."pg".active=true 将仅使 PostgreSQL 持久性单元和数据源可用,而运行时的 quarkus.hibernate-orm."oracle".active=truequarkus.datasource."oracle".active=true 将仅使 Oracle 持久性单元和数据源可用。

Custom configuration profiles可以帮助简化这样的设置。通过将以下特定于配置文件的配置附加到上面的配置,您可以简单地通过 setting quarkus.profile: `quarkus.profile=prod,pg`或 `quarkus.profile=prod,oracle`选择一个持久性单元/datasource。

%pg.quarkus.hibernate-orm."pg".active=true
%pg.quarkus.datasource."pg".active=true
# Add any pg-related runtime configuration here, prefixed with "%pg."

%oracle.quarkus.hibernate-orm."oracle".active=true
%oracle.quarkus.datasource."oracle".active=true
# Add any pg-related runtime configuration here, prefixed with "%pg."

定义重定向至当前活动持久性单元的 CDI bean producer 也很有帮助,如下所示:

public class MyProducer {
    @Inject
    DataSourceSupport dataSourceSupport;

    @Inject
    @PersistenceUnit("pg")
    Session pgSessionBean;

    @Inject
    @PersistenceUnit("oracle")
    Session oracleSessionBean;

    @Produces
    @ApplicationScoped
    public Session session() {
        if (dataSourceSupport.getInactiveNames().contains("pg")) {
            return oracleSessionBean;
        } else {
            return pgSessionBean;
        }
    }
}

Setting up and configuring Hibernate ORM with a persistence.xml

或者,可以使用 META-INF/persistence.xml 来设置 Hibernate ORM。此方法适用于:

  • migrating existing code

  • 您有相对复杂且需要配置全部灵活性的设置时

  • 或者如果您喜欢传统的方法

如果你使用 persistence.xml,那么就无法使用 quarkus.hibernate-orm.* 属性,并且只考虑在 persistence.xml 中定义的可持久化单元。 如果您的类路径包含要忽略的 persistence.xml,请设置以下配置属性:

quarkus.hibernate-orm.persistence-xml.ignore=true

你的 pom.xml 依赖项和 Java 代码将与此前示例相同。唯一不同的是,你可以在 META-INF/persistence.xml 中指定 Hibernate ORM 配置:

Example persistence.xml resource
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
             http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
             version="2.1">

    <persistence-unit name="CustomerPU" transaction-type="JTA">

        <description>My customer entities</description>

        <properties>
            <!-- Connection specific -->
            <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>

            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>

            <!--
                Optimistically create the tables;
                will cause background errors being logged if they already exist,
                but is practical to retain existing data across runs (or create as needed) -->
            <property name="jakarta.persistence.schema-generation.database.action" value="drop-and-create"/>

            <property name="jakarta.persistence.validation.mode" value="NONE"/>
        </properties>

    </persistence-unit>
</persistence>

当你使用 persistence.xml 配置时,你会直接配置 Hibernate ORM,因此在这种情况下的适当参考是 documentation on hibernate.org

请记住,这些不是与 Quarkus application.properties 使用的属性名称相同的属性名称,也不会应用相同的默认值。

XML mapping

Quarkus 中的 Hibernate ORM 支持 XML 映射。你可以按照 orm.xml format (Jakarta Persistence)hbm.xml format (specific to Hibernate ORM, deprecated) 添加映射文件:

  • application.properties 中通过(构建时) quarkus.hibernate-orm.mapping-files 属性。

  • 在 <<`persistence.xml`,persistence-xml>> 中通过 &lt;mapping-file&gt; 元素。

在构建时解析 XML 映射文件。

如果 META-INF/orm.xml 文件存在于类路径中,那么它将始终按默认设置包括在内。 如果这不是你想要的结果,请使用 quarkus.hibernate-orm.mapping-files = no-file<mapping-file>no-file</mapping-file>

Defining entities in external projects or jars

Quarkus 中的 Hibernate ORM 依赖于实体的编译期字节码增强。如果你在构建你的 Quarkus 应用程序的同一项目中定义你的实体,那么一切都会正常运行。

如果实体来自外部项目或 jar,你可以通过添加一个空的 META-INF/beans.xml 文件来确保你的 jar 像 Quarkus 应用程序库一样被处理。

这将允许 Quarkus 索引和增强你的实体,就好像它们在当前项目中一样。

Hibernate ORM in development mode

Quarkus 开发模式对于混合前端或服务和数据库访问的应用程序非常有用。

有一些常见方法可以充分利用它。

第一种选择是将 quarkus.hibernate-orm.database.generation=drop-and-createimport.sql 结合使用。

这样,对于你的应用程序的每一次更改,尤其是对你的实体的更改,数据库模式将被正确重建,并且你的数据固定装置(存储在 import.sql 中)将被用于从头开始重新填充它。这最适合完全控制你的环境,并且适用于 Quarkus 热重载模式:你的实体更改或对 import.sql 的任何更改都会立即被采纳,并且模式会在不重启应用程序的情况下更新!

devtest 模式下,默认情况下,Hibernate ORM 在启动后,将读取和执行 /import.sql 文件中的 SQL 语句(如果存在)。你可以修改 application.properties 中的属性 quarkus.hibernate-orm.sql-load-script 来更改该文件的文件名。

第二种方法是使用 quarkus.hibernate-orm.database.generation=update。当你要进行许多实体更改,但仍然需要处理生产数据的副本,或者当你想重现一个基于特定数据库条目的错误时,这种方法是最好的。update 是 Hibernate ORM 尽力想要达成的,但它会在特定情况下失败,包括会带来数据丢失的更改数据库结构。例如,如果你更改了违反外键约束的结构,Hibernate ORM 可能会被迫退出。但在开发中,这些限制是可以接受的。

第三种方法是使用 quarkus.hibernate-orm.database.generation=none。当你要处理生产数据的一个副本,但是希望完全控制架构演进时,这种方法是最好的。或者,如果你使用的数据库架构迁移工具类似于 FlywayLiquibase

使用此方法在对实体进行更改时,确保相应地调整数据库架构;你也可以使用 validate 同时调整 Hibernate 以验证架构是否符合它的预期。

不要在你的生产环境中使用 quarkus.hibernate-orm.database.generation drop-and-createupdate

当与 Quarkus 配置文件合并时,这些方法会变得非常强大。你可以定义不同的 configuration profiles 以根据你的环境选择不同的行为。这对你是极好的,因为你可以定义不同的 Hibernate ORM 属性组合,从而符合你当前需要的开发风格。

application.properties
%dev.quarkus.hibernate-orm.database.generation = drop-and-create
%dev.quarkus.hibernate-orm.sql-load-script = import-dev.sql

%dev-with-data.quarkus.hibernate-orm.database.generation = update
%dev-with-data.quarkus.hibernate-orm.sql-load-script = no-file

%prod.quarkus.hibernate-orm.database.generation = none
%prod.quarkus.hibernate-orm.sql-load-script = no-file

你可以使用自定义配置文件启动开发模式:

include::./_includes/devtools/dev.adoc[]:!dev-additional-parameters:

Hibernate ORM in production mode

Quarkus 带有默认配置文件(devtest`和 `prod)。且你可以添加你自己的自定义配置文件来描述多种环境(staging、`prod-us`等)。

Hibernate ORM Quarkus 扩展程序在开发和测试模式中将一些默认配置设置得不同于其他环境。

  • 对于除 devtest 外的所有配置文件,quarkus.hibernate-orm.sql-load-script 被设置为 no-file

你可以在你的 application.properties 中明确地覆盖它(例如 %prod.quarkus.hibernate-orm.sql-load-script = import.sql),但我们希望你避免在生产中意外覆盖你的数据库 :)

说起来,确保在生产中不要放弃你的数据库架构!在你的属性文件中添加以下内容。

application.properties
%prod.quarkus.hibernate-orm.database.generation = none
%prod.quarkus.hibernate-orm.sql-load-script = no-file

Automatically transitioning to Flyway to Manage Schemas

如果你在运行开发模式时安装了 Flyway extension,Quarkus 提供了一种简单的方法来初始化你的 Flyway 配置,其中使用 Hibernate ORM 自动生成的架构。这旨在减轻从早期开发阶段(其中 Hibernate 可以用来快速建立架构)到生产阶段(其中 Flyway 用于管理架构更改)的过渡。

若要使用此功能,只需在安装 quarkus-flyway 扩展程序后打开开发 UI,然后单击 Flyway 窗格中的 Datasources 链接。点击 Create Initial Migration 按钮,将发生以下情况:

  • 将创建一个 db/migration/V1.0.0__{appname}.sql 文件,其中包含 Hibernate 正在运行的 SQL 以生成架构

  • 将设置 quarkus.flyway.baseline-on-migrate,指示 Flyway 自动创建其基线表

  • 将设置 quarkus.flyway.migrate-at-start,指示 Flyway 在应用程序启动时自动应用迁移

  • 将设置 %dev.quarkus.flyway.clean-at-start%test.quarkus.flyway.clean-at-start,以便在开发/测试模式中重新加载后清除 DB

此按钮只是一种便利的功能,可以帮助你快速上手 Flyway,由你来确定如何在生产中管理你的数据库架构。特别是,migrate-at-start 设置可能并不适用于所有环境。

Caching

当 Hibernate ORM 二级缓存启用时,那些频繁读取相同实体的应用程序可以提高性能。

Caching of entities

要启用二级缓存,请使用 `@jakarta.persistence.Cacheable`标记你希望缓存的实体:

@Entity
@Cacheable
public class Country {
    int dialInCode;
    // ...
}

当某个实体使用 `@Cacheable`进行了注释时,除集合和其他实体的关系之外的所有字段值均会被缓存。

这意味着实体可以在不查询数据库的情况下加载,但需要注意的是这说明加载的实体可能无法反映数据库中的近期更改。

Caching of collections and relations

集合和关系需要单独进行注释以进行缓存;在这种情况下,应该使用 Hibernate 特定的 @org.hibernate.annotations.Cache,它还需要指定 CacheConcurrencyStrategy

package org.acme;

@Entity
@Cacheable
public class Country {
    // ...

    @OneToMany
    @Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
    List<City> cities;

    // ...
}

Caching of queries

查询也可从二级缓存中受益。缓存查询结果可以立即返回给调用方,避免在数据库中运行查询。

需要注意的是这说明结果可能无法反映近期更改。

若要缓存查询,请在 `Query`实例上将此查询标记为可缓存的:

Query query = ...
query.setHint("org.hibernate.cacheable", Boolean.TRUE);

如果你有一个 NamedQuery,则可以在其定义(通常会在实体上)直接启用缓存:

@Entity
@NamedQuery(name = "Fruits.findAll",
      query = "SELECT f FROM Fruit f ORDER BY f.name",
      hints = @QueryHint(name = "org.hibernate.cacheable", value = "true") )
public class Fruit {
   ...

就是这样!缓存技术在 Quarkus 中已经默认集成并启用,因此只需要设置哪些内容可以安全缓存。

Tuning of Cache Regions

缓存将数据存储在独立的区域中以隔离不同的数据部分;为这些区域分配了一个名称,这对于独立配置每个区域或监视其统计数据非常有用。

默认情况下,实体会缓存到以其限定名称命名的区域中,例如 org.acme.Country.

集合缓存到以其所有者实体限定名命名的区域中,该限定名以 #`字符与集合字段名分隔,例如 `org.acme.Country#cities.

默认情况下,所有缓存查询都会保存在指定的单个区域 `default-query-results-region`中。

所有区域默认都会受到大小和时间的限制。默认值为 `10000`个最大条目,最大空闲时间为 `100`秒。

可以通过 `quarkus.hibernate-orm.cache."<region_name>".memory.object-count`属性自定义每个区域的大小(使用实际区域名称替换 <region_name>)。

要设置最大空闲时间,请通过 `quarkus.hibernate-orm.cache."<region_name>".expiration.max-idle`属性提供持续时间(在下面参见关于持续时间格式的注释)(使用实际区域名称替换 <region_name>)。

如果你的区域名称包含一个点,则必须使用双引号。例如:

quarkus.hibernate-orm.cache."org.acme.MyEntity".memory.object-count=1000

要编写持续时间值,需使用标准格式 java.time.Duration.更多信息,请参阅 Duration#parse() javadoc. 你还可以使用简化格式,从数字开始:

  • 如果该值为数字,则表示时间(以秒计)。

  • 如果该值为数字,后跟 ms,则表示时间(以毫秒计)。

在其他情况下,简化格式会转换为 java.time.Duration 格式进行解析:

  • 如果该值为数字,后跟 h, ms, 则前缀为 PT.

  • 如果该值为数字,后跟 d ,则前缀为 P.

Limitations of Caching

目前 Quarkus 提供的缓存技术相当基础,限制也很大。

团队认为,从一开始就拥有 some 缓存功能总比一无所获要好;您可以期待在将来的版本中整合更好的缓存解决方案,并且非常欢迎在此领域提供任何帮助和反馈。

这些缓存被本地保存,因此当其他应用程序对持久化存储进行更改时,它们不会失效或更新。 另外,当运行应用程序的多个副本(在群集中,例如在 Kubernetes/OpenShift 上)时,应用程序不同副本中的缓存不会同步。 由于这些原因,只有在可以做出某些假设时才适合启用缓存:我们强烈建议只缓存从不更改的实体、集合和查询。或者至多是,当此类实体确实已变异并允许过时(陈旧)读取时,这不会影响应用程序的预期。 遵循此建议可确保应用程序从二级缓存中获得最佳性能,同时避免意外行为。 除了不可变数据外,在某些情况下,在可变数据上启用缓存也可能是可以接受的;这可能是对经常读取且可以接受一定程度的数据陈旧的选定实体的必要权衡;通过设置驱逐属性,可以调整这种“可接受的陈旧程度”。然而,不建议这样做,并且应该极其谨慎地进行,因为它可能会对数据产生意外且不可预见的影响。 与其在可变数据上启用缓存,理想情况下更好的解决方案是使用群集缓存;然而在目前,Quarkus 没有提供此类实现:随时联系我们并告知此需求,以便团队能够考虑这一点。

最后,通过将 hibernate.cache.use_second_level_cache 设置为 false,可以在全局范围内禁用二级缓存;这是一个需要在 persistence.xml 配置文件中指定的设置。

当禁用二级缓存时,将忽略所有缓存批注,并且所有查询都会在忽略缓存的情况下运行;这通常只对诊断问题有用。

Hibernate Envers

Hibernate ORM 的 Envers 扩展旨在为实体类提供简单的审计/版本控制解决方案。

在 Quarkus 中,Envers 拥有一个专门的 Quarkus 扩展 io.quarkus:quarkus-hibernate-envers;您只需将此扩展添加到项目即可开始使用。

Additional dependency to enable Hibernate Envers
    <!-- Add the Hibernate Envers extension -->
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-hibernate-envers</artifactId>
    </dependency>

此时,该扩展不会公开其他配置属性。

有关 Hibernate Envers 的更多信息,请参阅 hibernate.org/orm/envers/

Metrics

MicrometerSmallRye Metrics 均能够公开 Hibernate ORM 在运行时收集的指标。要启用在 /q/metrics 端点上的 Hibernate 指标公开,请确保您的项目依赖于指标扩展,并将配置属性 quarkus.hibernate-orm.metrics.enabled 设置为 true。在使用 SmallRye Metrics 时,指标将在 vendor 范围内可用。

Limitations and other things you should know

Quarkus 不会修改它使用的库;此规则也适用于 Hibernate ORM:在使用此扩展时,您将主要拥有与使用原始库相同的体验。

但虽然它们共享相同的代码,但 Quarkus 确实会自动配置某些组件,并为某些扩展点注入自定义实现;这应该是透明且有用的,但如果您是 Hibernate 的专家,您可能希望了解正在执行的操作。

Automatic build time enhancement

Hibernate ORM 可以使用构建时增强的实体;通常这不是强制性的,但它非常有用且可以让您的应用程序性能表现得更好。

通常,您需要调整构建脚本以包含 Hibernate 增强插件;在 Quarkus 中,由于增强步骤已集成到 Quarkus 应用程序的构建和分析中,因此无需进行此操作。

由于使用增强,目前不支持在实体上使用 @[1] 方法,因为它还会克隆一些特定于实体的增强特定字段。 此限制可能在将来移除。

Automatic integration

Transaction Manager integration

You don’t need to set this up, Quarkus automatically injects the reference to the Narayana Transaction Manager. The dependency is included automatically as a transitive dependency of the Hibernate ORM extension. All configuration is optional; for more details see Using Transactions in Quarkus.

Connection pool

Don’t need to choose one either. Quarkus automatically includes the Agroal connection pool; configure your datasource as in the above examples and it will set up Hibernate ORM to use Agroal. More details about this connection pool can be found in Quarkus - Datasources.

Second Level Cache

As explained earlier in the caching, you don’t need to pick an implementation. A suitable implementation based on technologies from Infinispan and Caffeine is included as a transitive dependency of the Hibernate ORM extension, and automatically integrated during the build.

Limitations

XML mapping with duplicate files in the classpath

xml-mapping files are expected to have a unique path.

实际上,只有在非常特殊的情况下,才可以在类路径中拥有重复的 XML 映射文件。例如,如果两个 JAR 包含一个 @[4] 文件(具有完全相同的路径,但位于不同的 JAR 中),则映射文件路径 @[5] 只能从 @[6]@[3] 引用。

JMX

Management beans are not working in GraalVM native images; therefore, Hibernate’s capability to register statistics and management operations with the JMX bean is disabled when compiling into a native image. This limitation is likely permanent, as it’s not a goal for native images to implement support for JMX. All such metrics can be accessed in other ways.

JACC Integration

Hibernate ORM’s capability to integrate with JACC is disabled when building GraalVM native images, as JACC is not available - nor useful - in native mode.

Binding the Session to ThreadLocal context

It is impossible to use the ThreadLocalSessionContext helper of Hibernate ORM as support for it is not implemented. Since Quarkus provides out-of-the-box CDI support, injection or programmatic CDI lookup is a better approach. This feature also didn’t integrate well with reactive components and more modern context propagation techniques, making us believe this legacy feature has no future. If you badly need to bind it to a ThreadLocal, it should be trivial to implement in your own code.

JNDI

The JNDI technology is commonly used in other runtimes to integrate different components. A common use case is Java Enterprise servers to bind the TransactionManager and the Datasource components to a name and then have Hibernate ORM configured to look these components up by name. But in Quarkus, this use case doesn’t apply as components are injected directly, making JNDI support an unnecessary legacy. To avoid unexpected use of JNDI, full support for JNDI has been disabled in the Hibernate ORM extension for Quarkus. This is both a security precaution and an optimization.

Other notable differences

Format of import.sql

When importing a import.sql to set up your database, keep in mind that Quarkus reconfigures Hibernate ORM so to require a semicolon (;) to terminate each statement. The default in Hibernate is to have a statement per line, without requiring a terminator other than newline: remember to convert your scripts to use the ; terminator character if you’re reusing existing scripts. This is useful so to allow multi-line statements and human friendly formatting.

Simplifying Hibernate ORM with Panache

@[7] 扩展通过提供活动记录样式实体(和存储库)来促进 Hibernate ORM 的使用,其重点是让实体在 Quarkus 中变得简单易写。

Configure your datasource

数据源配置非常简单,但它包含在一个不同的指南中,因为从技术上来说它是由 Quarkus 的 Agroal 连接池扩展实现的。

跳转至 @[8] 了解所有详细信息。

Multitenancy

“术语多租户通常适用于软件开发,用于表示应用程序的单一运行实例同时为多个客户端(租户)提供服务的一种架构。这在 SaaS 解决方案中非常常见。隔离与各个租户有关的信息(数据、自定义项等)是这些系统中的一个特殊挑战。其中包括存储在数据库中的由各个租户拥有的数据”(@[9])。

Quarkus 目前支持 @[10] 方法、@[11] 方法和 @[12] 方法。

要查看多租户的运行情况,您可以查看 @[13] 快速入门。

Writing the application

让我们从实现 @[14] 端点开始。从下面的源代码中可以看到,它只是一个常规的 Jakarta REST 资源:

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

@ApplicationScoped
@Path("/{tenant}")
public class FruitResource {

    @Inject
    EntityManager entityManager;

    @GET
    @Path("fruits")
    public Fruit[] getFruits() {
        return entityManager.createNamedQuery("Fruits.findAll", Fruit.class)
                .getResultList().toArray(new Fruit[0]);
    }

}

为了从传入请求中解决租户并将它映射到特定的租户配置,您需要为 @[15] 接口创建实现。

import jakarta.enterprise.context.ApplicationScoped;

import io.quarkus.hibernate.orm.runtime.tenant.TenantResolver;
import io.vertx.ext.web.RoutingContext;

@PersistenceUnitExtension (1)
@RequestScoped (2)
public class CustomTenantResolver implements TenantResolver {

    @Inject
    RoutingContext context;

    @Override
    public String getDefaultTenantId() {
        return "base";
    }

    @Override
    public String resolveTenantId() {
        String path = context.request().path();
        String[] parts = path.split("/");

        if (parts.length == 0) {
            // resolve to default tenant config
            return getDefaultTenantId();
        }

        return parts[1];
    }

}
1 使用 @[16] 限定符为 TenantResolver 实现添加注释,以告知 Quarkus 它应在默认持久化单元中使用。对于 @[18],使用 @[17]
2 bean 被制作成 @[19],因为租户解决取决于传入请求。

从上面的实现来看,租户是从请求路径解决的,这样即使无法推断出租户,也会返回默认租户标识符。

如果您还使用 @[22],并且 OIDC 和 Hibernate ORM 租户 ID 相同,并且必须从 Vert.x @[20] 中提取,则您可以将租户 ID 从 OIDC Tenant Resolver 传递给 Hibernate ORM Tenant Resolver,作为 @[21] 属性,例如:

import io.quarkus.hibernate.orm.runtime.tenant.TenantResolver;
import io.vertx.ext.web.RoutingContext;

@PersistenceUnitExtension
@RequestScoped
public class CustomTenantResolver implements TenantResolver {

    @Inject
    RoutingContext context;
    ...
    @Override
    public String resolveTenantId() {
        // OIDC TenantResolver has already calculated the tenant id and saved it as a RoutingContext `tenantId` attribute:
        return context.get("tenantId");
    }
}

Configuring the application

一般来说,Hibernate ORM 数据库生成功能无法与多租户设置结合使用。因此,必须禁用该功能,并确保按架构创建表。以下设置将使用 Flyway 扩展来实现此目标。

SCHEMA approach

所有租户将使用相同的数据源,并且必须在该数据源内为每个租户创建架构。

MariaDB/MySQL 等某些数据库不支持数据库架构。在这些情况下,必须使用 database approach

quarkus.hibernate-orm.database.generation=none 1

quarkus.hibernate-orm.multitenant=SCHEMA 2

quarkus.datasource.db-kind=postgresql 3
quarkus.datasource.username=quarkus_test
quarkus.datasource.password=quarkus_test
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/quarkus_test

quarkus.flyway.schemas=base,mycompany 4
quarkus.flyway.locations=classpath:schema
quarkus.flyway.migrate-at-start=true
1 禁用架构生成,因为它不受 Hibernate ORM 对多租户架构的支持。我们将使用 Flyway,见下文。
2 Enable schema multi-tenancy.我们在此使用默认数据源,但如果愿意,可以通过按照 there 中的说明来使用已命名的数据源。
3 Configure the datasource.
4 配置 Flyway 以进行数据库初始化,因为在这种情况下 Hibernate ORM 不支持架构生成。

以下是将在所配置的文件夹 src/main/resources/schema 中创建的 Flyway SQL (V1.0.0__create_fruits.sql ) 示例。

CREATE SEQUENCE base.known_fruits_id_seq;
SELECT setval('base."known_fruits_id_seq"', 3);
CREATE TABLE base.known_fruits
(
  id   INT,
  name VARCHAR(40)
);
INSERT INTO base.known_fruits(id, name) VALUES (1, 'Cherry');
INSERT INTO base.known_fruits(id, name) VALUES (2, 'Apple');
INSERT INTO base.known_fruits(id, name) VALUES (3, 'Banana');

CREATE SEQUENCE mycompany.known_fruits_id_seq;
SELECT setval('mycompany."known_fruits_id_seq"', 3);
CREATE TABLE mycompany.known_fruits
(
  id   INT,
  name VARCHAR(40)
);
INSERT INTO mycompany.known_fruits(id, name) VALUES (1, 'Avocado');
INSERT INTO mycompany.known_fruits(id, name) VALUES (2, 'Apricots');
INSERT INTO mycompany.known_fruits(id, name) VALUES (3, 'Blackberries');

DATABASE approach

对于每个租户,都需要创建一个命名的数据源,其中标识符由 TenantResolver 返回。

使用此方法时,假定由同一持久单元使用的所有数据源指向相同供应商(相同 db-kind )和版本的数据库。 不检测不匹配,并且可能会导致不可预测的行为。

quarkus.hibernate-orm.database.generation=none 1

quarkus.hibernate-orm.multitenant=DATABASE 2
quarkus.hibernate-orm.datasource=base 3

# Default tenant 'base'
quarkus.datasource.base.db-kind=postgresql 4
quarkus.datasource.base.username=quarkus_test
quarkus.datasource.base.password=quarkus_test
quarkus.datasource.base.jdbc.url=jdbc:postgresql://localhost:5432/quarkus_test
quarkus.flyway.base.locations=classpath:database/base 5
quarkus.flyway.base.migrate-at-start=true

# Tenant 'mycompany'
quarkus.datasource.mycompany.db-kind=postgresql 6
quarkus.datasource.mycompany.username=mycompany
quarkus.datasource.mycompany.password=mycompany
quarkus.datasource.mycompany.jdbc.url=jdbc:postgresql://localhost:5433/mycompany
quarkus.flyway.mycompany.locations=classpath:database/mycompany 7
quarkus.flyway.mycompany.migrate-at-start=true
1 禁用架构生成,因为它不受 Hibernate ORM 对数据库多租户的支持。我们将使用 Flyway,见下文。
2 Enable database multi-tenancy.
3 为持久单元选择一个数据源。这只是为了允许 Quarkus 确定要使用的 Hibernate ORM 方言;有关详细信息,请参见 this section
4 对于一个租户,Configure the datasourcebase
5 配置 Flyway 以进行租户 base 的数据库初始化,因为在这种情况下不支持 Hibernate ORM 的架构生成。
6 对于另一个租户,Configure the datasource 。可能还有更多租户,但这里我们停在两个。
7 配置 Flyway 以进行租户 mycompany 的数据库初始化,因为在这种情况下不支持 Hibernate ORM 的架构生成。

以下是 Flyway SQL 文件的示例,可在已配置文件夹 src/main/resources/database 中创建。

base (src/main/resources/database/base/V1.0.0__create_fruits.sql) 租户架构:

CREATE SEQUENCE known_fruits_id_seq;
SELECT setval('known_fruits_id_seq', 3);
CREATE TABLE known_fruits
(
  id   INT,
  name VARCHAR(40)
);
INSERT INTO known_fruits(id, name) VALUES (1, 'Cherry');
INSERT INTO known_fruits(id, name) VALUES (2, 'Apple');
INSERT INTO known_fruits(id, name) VALUES (3, 'Banana');

mycompany (src/main/resources/database/mycompany/V1.0.0__create_fruits.sql) 租户架构:

CREATE SEQUENCE known_fruits_id_seq;
SELECT setval('known_fruits_id_seq', 3);
CREATE TABLE known_fruits
(
  id   INT,
  name VARCHAR(40)
);
INSERT INTO known_fruits(id, name) VALUES (1, 'Avocado');
INSERT INTO known_fruits(id, name) VALUES (2, 'Apricots');
INSERT INTO known_fruits(id, name) VALUES (3, 'Blackberries');

DISCRIMINATOR approach

将针对所有租户使用默认数据源。所有定义了用 @TenantId 注释的字段的实体都将自动填充该字段,并在查询中自动过滤。

quarkus.hibernate-orm.multitenant=DISCRIMINATOR 1

quarkus.datasource.db-kind=postgresql 2
quarkus.datasource.username=quarkus_test
quarkus.datasource.password=quarkus_test
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/quarkus_test
1 Enable discriminator multi-tenancy.
2 Configure the datasource.

Programmatically Resolving Tenants Connections

如果您需要对想要支持的不同租户使用更动态的配置并且不想在配置文件中结束多个条目,那么可以使用 io.quarkus.hibernate.orm.runtime.tenant.TenantConnectionResolver 接口实现自己的逻辑来检索连接。创建一个实现了此接口且用 @PersistenceUnitExtension 注释的应用程序范围 bean(或者用 @PersistenceUnitExtension("nameOfYourPU") 注释,对于 named persistence unit),将替换当前 Quarkus 默认实现 io.quarkus.hibernate.orm.runtime.tenant.DataSourceTenantConnectionResolver。自定义连接解析器可以例如从数据库中读取租户信息并根据此信息在运行时为每个租户创建一个连接。

Interceptors

您可以通过使用适当限定符定义 CDI Bean 来为 SessionFactory 分配 org.hibernate.Interceptor

@PersistenceUnitExtension (1)
public static class MyInterceptor extends EmptyInterceptor { (2)
    @Override
    public boolean onLoad(Object entity, Serializable id, Object[] state, (3)
            String[] propertyNames, Type[] types) {
        // ...
        return false;
    }
}
1 使用 @PersistenceUnitExtension 限定符注释拦截器实现来告诉 Quarkus 应在默认持久性单元中使用它。对于 named persistence units,请使用 @PersistenceUnitExtension("nameOfYourPU")
2 扩展 org.hibernate.EmptyInterceptor 或直接实现 org.hibernate.Interceptor
3 Implement methods as necessary.

默认情况下,使用 @PersistenceUnitExtension 注释的拦截器 bean 是应用程序范围的,这意味着每个应用程序只创建一个拦截器实例,并在所有实体管理器中重复使用。由于此原因,拦截器实现必须是线程安全的。 为了改为为每个实体管理器创建一个拦截器实例,请使用 @Dependent 注释您的 bean。在这种情况下,拦截器实现不需要是线程安全的。

由于 Hibernate ORM 本身存在限制,@Dependent 范围拦截器上的 @PreDestroy 方法永远不会被调用。

Statement Inspectors

您可以通过简单地定义具有适当限定符的 CDI bean 来为您 SessionFactory 中的 org.hibernate.engine.jdbc.spi.StatementInspector 分配一个:

@PersistenceUnitExtension (1)
public class MyStatementInspector implements StatementInspector { (2)
    @Override
    public String inspect(String sql) {
        // ...
        return sql;
    }
}
1 使用 @PersistenceUnitExtension 限定符注释语句检查器实现来告诉 Quarkus 应在默认持久性单元中使用它。对于 named persistence units,请使用 @PersistenceUnitExtension("nameOfYourPU")
2 Implement org.hibernate.engine.jdbc.spi.StatementInspector.

Customizing JSON/XML serialization/deserialization

默认情况下,Quarkus 将根据可用的扩展自动配置格式映射器。当 Jackson(或 JSON-B)可用时,全局配置的 ObjectMapper (或 Jsonb) 将用于序列化/反序列化操作。如果 Jackson 和 JSON-B 同时可用,Jackson 将优先。

可以通过实现 org.hibernate.type.format.FormatMapper 并使用适当的限定符注释实现来定制 Hibernate ORM 中的 JSON 和 XML 序列化/反序列化:

@JsonFormat (1)
@PersistenceUnitExtension (2)
public class MyJsonFormatMapper implements FormatMapper { (3)
    @Override
    public <T> T fromString(CharSequence charSequence, JavaType<T> javaType, WrapperOptions wrapperOptions) {
        // ...
    }

    @Override
    public <T> String toString(T value, JavaType<T> javaType, WrapperOptions wrapperOptions) {
       // ...
    }
}
1 使用 @JsonFormat 限定符注释格式映射器实现来告诉 Quarkus 此映射器特定于 JSON 序列化/反序列化。
2 使用 @PersistenceUnitExtension 限定符注释格式映射器实现来告诉 Quarkus 应在默认持久性单元中使用它。对于 named persistence units,请使用 @PersistenceUnitExtension("nameOfYourPU")
3 Implement org.hibernate.type.format.FormatMapper.

对于自定义 XML 格式映射器,必须应用不同的 CDI 限定符:

@XmlFormat (1)
@PersistenceUnitExtension (2)
public class MyJsonFormatMapper implements FormatMapper { (3)
    @Override
    public <T> T fromString(CharSequence charSequence, JavaType<T> javaType, WrapperOptions wrapperOptions) {
        // ...
    }

    @Override
    public <T> String toString(T value, JavaType<T> javaType, WrapperOptions wrapperOptions) {
       // ...
    }
}
1 使用 `@XmlFormat`限定符为格式映射器实现添加注解,告知 Quarkus 此映射器特定于 XML 序列化/反序列化。
2 使用 @PersistenceUnitExtension 限定符注释格式映射器实现来告诉 Quarkus 应在默认持久性单元中使用它。对于 named persistence units,请使用 @PersistenceUnitExtension("nameOfYourPU")
3 Implement org.hibernate.type.format.FormatMapper.

格式映射器 *must*既拥有 `@PersistenceUnitExtension`限定符,又拥有 `@JsonFormat`或 `@XmlFormat`CDI 限定符。 因为会产生歧义,所以如果为同一持久性单元注册了多个 JSON(或 XML)格式映射器,则会引发异常。