使用 Hibernate ORM 和 Jakarta Persistence
Hibernate ORM 是事实上的 Jakarta Persistence(以前称为 JPA)标准实现,它为您提供了对象关系映射器的全部功能。它在 Quarkus 中运行得非常好。
解决方案
我们建议您按照以下章节中的说明,逐步创建应用程序。但是,您可以直接转到完整的示例。
克隆 Git 存储库: git clone https://github.com/quarkusio/quarkus-quickstarts.git
,或者下载一个存档。
解决方案位于 hibernate-orm-quickstart
目录中。
设置和配置 Hibernate ORM
在 Quarkus 中使用 Hibernate ORM 时,您无需 persistence.xml
资源来配置它。
使用这种经典的配置文件也是一种选择,但除非您有特殊的进阶需求,否则是不必要的;因此,我们将首先介绍如何在没有 persistence.xml
资源的情况下配置 Hibernate ORM。
在 Quarkus 中,您只需要
-
将您的配置设置添加到
application.properties
-
像往常一样用
@Entity
和其他任何映射注解来注解您的实体
其他配置需求已实现自动化:Quarkus 将会做出一些有倾向性的选择和合理的猜测。
将以下依赖项添加到您的项目
-
Hibernate ORM 扩展:
io.quarkus:quarkus-hibernate-orm
-
您的 JDBC 驱动程序扩展;以下选项可用
-
quarkus-jdbc-db2
用于IBM DB2 -
quarkus-jdbc-derby
用于Apache Derby -
quarkus-jdbc-h2
用于H2 -
quarkus-jdbc-mariadb
用于MariaDB -
quarkus-jdbc-mssql
用于Microsoft SQL Server -
quarkus-jdbc-mysql
用于MySQL -
quarkus-jdbc-oracle
用于Oracle Database -
quarkus-jdbc-postgresql
用于PostgreSQL
-
例如
<!-- 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>
// Hibernate ORM specific dependencies
implementation("io.quarkus:quarkus-hibernate-orm")
// JDBC driver dependencies
implementation("io.quarkus:quarkus-jdbc-postgresql")
用 @Entity
注解您的持久化对象,然后将相关的配置属性添加到 application.properties
。
application.properties
quarkus.datasource.db-kind = postgresql (1)
quarkus.hibernate-orm.schema-management.strategy=drop-and-create (2)
%prod.quarkus.datasource.username = hibernate
%prod.quarkus.datasource.password = hibernate
%prod.quarkus.datasource.jdbc.url = jdbc:postgresql://:5432/hibernate_db
1 | 配置数据源. |
2 | 在启动时删除并创建数据库(使用 update 仅更新模式)。 |
请注意,这些配置属性与您典型的 Hibernate ORM 配置文件中的属性不同。它们通常会映射到 Hibernate ORM 的配置属性,但可能具有不同的名称,并且不一定是一对一映射的。
此外,Quarkus 会自动设置许多 Hibernate ORM 配置设置,并且通常会使用更现代的默认值。
有关可以在 application.properties
中设置的项列表,请参阅Hibernate ORM 配置属性。
只要 Hibernate ORM 扩展列在您的项目依赖项中,就会根据 Quarkus datasource
配置创建一个 EntityManagerFactory
。
方言将根据您的数据源自动选择和配置;您可能需要将其配置为更精确地匹配您的数据库。
然后,您可以愉快地注入您的 EntityManager
@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 | 将您的 CDI bean 方法标记为 @Transactional ,EntityManager 将在提交时参与并刷新。 |
@Entity
public class Gift {
private Long id;
private String name;
@Id
@GeneratedValue
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;
}
}
要加载 SQL 语句并在 Hibernate ORM 启动时执行,请在 resources
目录的根目录下添加一个 import.sql
文件。此脚本可以包含任何 SQL DML 语句。确保每个语句都以分号结尾。
这对于拥有现成的数据集用于您的测试或演示非常有用。
请确保将修改数据库的方法(例如 entity.persist() )包装在事务中。将 CDI bean 方法标记为 @Transactional 将为您完成此操作,并使该方法成为事务边界。我们建议在您的应用程序入口点边界(例如 REST 端点控制器)这样做。 |
方言
支持的数据库
对于支持的数据库,不需要显式设置Hibernate ORM 方言:它将根据数据源自动选择。
默认情况下,方言配置为针对数据库的最低支持版本。
为了让 Hibernate ORM 生成更有效的 SQL,避免变通方法并利用更多数据库功能,您可以显式设置数据库版本
db-version
的 application.properties
quarkus.datasource.db-kind = postgresql
quarkus.datasource.db-version = 14.0 (1)
%prod.quarkus.datasource.username = hibernate
%prod.quarkus.datasource.password = hibernate
%prod.quarkus.datasource.jdbc.url = jdbc:postgresql://:5432/hibernate_db
1 | 设置数据库版本。Hibernate ORM 方言将针对该版本。 |
通常,此处设置的版本应尽可能高,但必须低于或等于应用程序将连接到的任何数据库的版本。
如上所述,版本可以通过 这是一个安全措施:对于比配置版本旧的数据库版本,Hibernate ORM 可能会生成无效的 SQL,这会导致运行时异常。 如果无法访问数据库,将记录一条警告消息,但启动会继续。如果知道数据库在启动时无法访问,您可以使用 |
其他数据库
如果您的数据库没有相应的 Quarkus 扩展,或者默认设置由于某种原因不满足您的需求,您需要显式设置Hibernate ORM 方言
dialect
的 application.properties
quarkus.datasource.db-kind = postgresql
quarkus.hibernate-orm.dialect=Cockroach (1)
%prod.quarkus.datasource.username = hibernate
%prod.quarkus.datasource.password = hibernate
%prod.quarkus.datasource.jdbc.url = jdbc:postgresql://:26257/hibernate_db
1 | 设置 Hibernate ORM 方言。 对于内置方言,期望值是官方方言列表中的名称之一,不包括 对于第三方方言,预期值是完全限定的类名,例如 |
在这种情况下,请记住 JDBC 驱动程序或 Hibernate ORM 方言在 GraalVM 原生可执行文件中可能无法正常工作。 |
与支持的数据库一样,您可以显式配置 DB 版本以充分利用 Hibernate ORM
dialect
和 db-version
的 application.properties
quarkus.datasource.db-kind = postgresql
quarkus.datasource.db-version = 22.2 (1)
quarkus.hibernate-orm.dialect=Cockroach (2)
%prod.quarkus.datasource.username = hibernate
%prod.quarkus.datasource.password = hibernate
%prod.quarkus.datasource.jdbc.url = jdbc:postgresql://:26257/hibernate_db
1 | 设置数据库版本。Hibernate ORM 方言将针对该版本。由于我们在这里针对 CockroachDB,因此我们传递的是 CockroachDB 版本,而不是 PostgreSQL 版本。 |
2 | 设置 Hibernate ORM 方言。 |
可变数据库
启用数据库多租户时,Hibernate ORM 将在运行时为同一持久化单元使用多个数据源,并且默认情况下 Quarkus 无法确定将使用哪个数据源,因此它将无法为 Hibernate ORM 检测到要使用的方言。
因此,在启用数据库多租户时,建议显式指向运行时将使用的其中一个数据源的 Hibernate ORM 配置,例如使用 quarkus.hibernate-orm.datasource=base
(base
是一个数据源的名称)。
这样做时,Quarkus 将从该数据源推断数据库版本(如果可能)和方言。对于不受支持的数据库,您可能仍需要显式设置 Hibernate ORM 方言,如本节所述。
Hibernate ORM 配置属性
有各种可选属性可用于细化您的 EntityManagerFactory
或指导 Quarkus 的猜测。
没有必需的属性,只要配置了默认数据源即可。
当未设置任何属性时,Quarkus 通常可以推断出设置 Hibernate ORM 所需的所有内容,并使其使用默认数据源。
此处列出的配置属性允许您覆盖此类默认值,并自定义和调整各个方面。
构建时固定的配置属性 - 所有其他配置属性都可以在运行时覆盖
配置属性 |
类型 |
默认 |
||
---|---|---|---|---|
在构建期间是否启用 Hibernate ORM。 如果在构建期间禁用了 Hibernate ORM,则所有与 Hibernate ORM 相关的处理都将被跳过,但在运行时无法激活 Hibernate ORM: 环境变量: 显示更多 |
布尔值 |
|
||
Hibernate ORM 是否以阻塞模式工作。 Hibernate ORM 的阻塞 环境变量: 显示更多 |
布尔值 |
|
||
如果为 环境变量: 显示更多 |
布尔值 |
|
||
是否启用统计信息收集。如果“metrics.enabled”为 true,则此处默认值被认为是 true,否则默认值为 false。 环境变量: 显示更多 |
布尔值 |
|||
是否应为每个 Hibernate 会话将会话指标追加到服务器日志中。仅当启用了统计信息( 环境变量: 显示更多 |
布尔值 |
|||
如果启用了指标扩展,是否发布指标。 环境变量: 显示更多 |
布尔值 |
|
||
允许在 Dev UI 页面中使用 hql 查询 环境变量: 显示更多 |
布尔值 |
|
||
启用或禁用对 Hibernate ORM 环境变量: 显示更多 |
布尔值 |
|
||
此持久化单元使用的数据源的名称。 如果未定义,它将使用默认数据源。 环境变量: 显示更多 |
字符串 |
|||
受此持久化单元影响的实体所在的包。 环境变量: 显示更多 |
字符串列表 |
|||
包含在 Hibernate ORM 启动时执行的 SQL 语句的文件的路径。 这些文件是从类路径资源中检索的,因此它们必须位于 resources 目录中(例如 此设置的默认值取决于 Quarkus 启动模式
如果您需要在开发模式、测试 ( application.properties
环境变量: 显示更多 |
字符串列表 |
|
||
用于应用数据库对象名称的物理命名规则的可插拔策略合同。 Hibernate PhysicalNamingStrategy 实现的类名 环境变量: 显示更多 |
字符串 |
|||
在未给出显式名称时,用于应用隐式命名规则的可插拔策略。 Hibernate ImplicitNamingStrategy 实现的类名 环境变量: 显示更多 |
字符串 |
|||
环境变量: 显示更多 |
字符串 |
|||
用于配置实体映射的 XML 文件,例如 如果存在,则默认为 环境变量: 显示更多 |
字符串列表 |
|
||
可以使用可用的策略之一来引用标识符。 默认设置为 环境变量: 显示更多 |
|
|
||
Quarkus 中的默认设置为启用 2 级缓存,并且已经为您集成了一个良好的实现。 只需挑出哪些实体应使用缓存。 将此设置为 false 以禁用所有 2 级缓存。 环境变量: 显示更多 |
布尔值 |
|
||
定义 Bean Validation 集成的行为方式。 环境变量: 显示更多 |
|
|
||
定义多租户的方法(DATABASE、NONE、SCHEMA)。允许值的完整列表可在Hibernate ORM JavaDoc 中找到。DISCRIMINATOR 类型目前不支持。默认值为 NONE(无多租户)。 环境变量: 显示更多 |
字符串 |
|||
如果 Hibernate 没有自动生成架构,并且 Quarkus 在开发模式下运行,则 Quarkus 将尝试在启动后验证数据库,并在出现任何问题时打印日志消息。 环境变量: 显示更多 |
布尔值 |
|
||
此持久化单元是否应在运行时处于活动状态。 请参阅本文档的此部分。 请注意,如果禁用了 Hibernate ORM(即 环境变量: 显示更多 |
布尔值 |
|
||
应直接传递给 Hibernate ORM 的属性。 在此处使用完整的配置属性键,例如
请考虑使用受支持的配置属性,然后再回退到不受支持的属性。如果没有,请务必提交功能请求,以便为 Quarkus 添加受支持的配置属性,更重要的是,以便定期测试该配置属性。 环境变量: 显示更多 |
Map<String,String> |
|||
类型 |
默认 |
|||
设置后,尽最大努力尝试与给定版本的 Hibernate ORM 进行数据交换。 请注意
环境变量: 显示更多 |
|
|
||
数据库的字符集。 用于 DDL 生成和 SQL 导入脚本。 环境变量: 显示更多 |
|
|||
用于数据库对象的默认目录。 环境变量: 显示更多 |
字符串 |
|||
用于数据库对象的默认架构。 环境变量: 显示更多 |
字符串 |
|||
Hibernate ORM 是否应在启动时检查数据库的版本是否与方言上配置的版本匹配(默认版本或通过 如果数据库在启动时不可用,则应将此设置为 环境变量: 显示更多 |
布尔值 |
|
||
类型 |
默认 |
|||
Hibernate ORM 方言的名称。 对于支持的数据库,此属性无需显式设置:它将根据数据源自动选择,并使用数据源上设置的 DB 版本进行配置,以获得最佳性能和最新功能。 如果您的数据库没有相应的 Quarkus 扩展,则需要显式设置此属性。 在这种情况下,请记住 JDBC 驱动程序和 Hibernate ORM 方言可能无法在 GraalVM 本机可执行文件中正常工作。 对于内置方言,期望值是官方方言列表中的名称之一,不包括 对于第三方方言,预期值是完全限定的类名,例如 环境变量: 显示更多 |
字符串 |
|
||
当方言支持多个存储引擎时,要使用的存储引擎。 例如 MySQL 的 环境变量: 显示更多 |
字符串 |
|||
类型 |
默认 |
|||
默认情况下如何为 可以使用
环境变量: 显示更多 |
|
|
||
要应用于其优化器未显式配置的标识符生成器的优化器。 仅与基于表和序列的标识符生成器相关。 其他生成器(例如基于 UUID 的生成器)将忽略此设置。 优化器负责汇集新的标识符值,以减少数据库调用的频率来检索这些值,从而提高性能。 环境变量: 显示更多 |
|
|
||
类型 |
默认 |
|||
查询计划缓存的最大大小。 请参阅 # 环境变量: 显示更多 |
整数 |
|
||
有效值为: 环境变量: 显示更多 |
|
|
||
启用 IN 子句参数填充,这可以提高语句缓存。 环境变量: 显示更多 |
布尔值 |
|
||
当无法在数据库端应用限制时,触发异常,而不是尝试性能不佳的内存结果集限制。 当分页与应用于集合或多值关联的提取连接结合使用时,限制必须在内存中应用,而不是在数据库中应用。 应避免这种情况,因为它通常具有很差的性能特征。 环境变量: 显示更多 |
布尔值 |
|
||
类型 |
默认 |
|||
推送到 JDBC 驱动程序的时区。 请参阅 环境变量: 显示更多 |
字符串 |
|||
JDBC 驱动程序一次提取多少行。 环境变量: 显示更多 |
整数 |
|||
JDBC 驱动程序一次发送多少更新(插入、更新和删除)来执行。 环境变量: 显示更多 |
整数 |
|||
类型 |
默认 |
|||
加载实体和集合时使用的批次的大小。
环境变量: 显示更多 |
整数 |
|
||
单端关联(一对一,多对一)的外连接提取树的最大深度。
环境变量: 显示更多 |
整数 |
|||
类型 |
默认 |
|||
缓存对象被视为过期的最大时间。 环境变量: 显示更多 |
||||
缓存中内存中保存的最大对象数。 环境变量: 显示更多 |
long |
|||
类型 |
默认 |
|||
现有应用程序依赖(隐式或显式)Hibernate 忽略连接继承层次结构上的任何 DiscriminatorColumn 声明。此设置允许这些应用程序维护 DiscriminatorColumn 注解在与连接继承配对时被忽略的传统行为。 环境变量: 显示更多 |
布尔值 |
|
||
类型 |
默认 |
|||
记录 SQL 绑定参数。 显然,不建议在生产环境中将其设置为 true。 环境变量: 显示更多 |
布尔值 |
|
||
显示 SQL 日志并很好地格式化它们。 显然,不建议在生产环境中将其设置为 true。 环境变量: 显示更多 |
布尔值 |
|
||
如果启用了 SQL 日志,则格式化 SQL 日志 环境变量: 显示更多 |
布尔值 |
|
||
如果启用了 SQL 日志,则高亮显示 SQL 日志 环境变量: 显示更多 |
布尔值 |
|
||
是否应该收集和记录 JDBC 警告。 环境变量: 显示更多 |
布尔值 |
|
||
如果设置,Hibernate 将记录执行时间超过指定毫秒数的查询。 环境变量: 显示更多 |
long |
|||
类型 |
默认 |
|||
选择是否生成数据库模式。
默认为“none”。 但是,如果 Dev Services 正在使用中,并且不存在其他管理模式的扩展,则该值将自动覆盖为“drop-and-create”。 可接受的值: 环境变量: 显示更多 |
字符串 |
|
||
如果 Hibernate ORM 应该自动创建模式(对于支持它们的数据库)。 环境变量: 显示更多 |
布尔值 |
|
||
在应用模式时,我们是否应该在第一个错误时停止。 环境变量: 显示更多 |
布尔值 |
|
||
类型 |
默认 |
|||
选择是否生成数据库模式 DDL 文件。可接受的值: 环境变量: 显示更多 |
字符串 |
|
||
应该在其中生成数据库创建 DDL 文件的文件名或 URL。 环境变量: 显示更多 |
字符串 |
|||
应该在其中生成数据库删除 DDL 文件的文件名或 URL。 环境变量: 显示更多 |
字符串 |
|||
类型 |
默认 |
|||
默认的刷新策略,或何时将实体刷新到 Hibernate 会话中的数据库:在每次查询之前,在提交时…… 可以通过 有关详细信息,请参阅 环境变量: 显示更多 |
|
|
关于 Duration 格式
要写入持续时间值,请使用标准的 您还可以使用简化的格式,以数字开头
在其他情况下,简化格式将被转换为
|
不要在 如果您希望忽略类路径中的
|
想用 Docker 在旁边启动一个 PostgreSQL 服务器吗?
这将启动一个非持久化的空数据库:非常适合快速实验! |
多个持久化单元
设置多个持久化单元
可以使用 Quarkus 配置属性定义多个持久化单元。
quarkus.hibernate-orm.
命名空间根目录下的属性定义了默认持久化单元。例如,以下代码片段定义了一个默认数据源和一个默认持久化单元
quarkus.datasource.db-kind=h2
quarkus.datasource.jdbc.url=jdbc:h2:mem:default;DB_CLOSE_DELAY=-1
quarkus.hibernate-orm.schema-management.strategy=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".schema-management.strategy=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".schema-management.strategy=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 数据源。 |
您可以混合使用默认数据源和命名数据源,或者只使用其中一个。 |
默认持久化单元默认指向默认数据源。对于命名持久化单元, 让多个持久化单元指向同一个数据源是完全有效的。 |
将模型类附加到持久化单元
有两种方法可以将模型类附加到持久化单元,并且不应混合使用
-
通过
packages
配置属性; -
通过
@io.quarkus.hibernate.orm.PersistenceUnit
包级别注解。
如果两者混合使用,注解将被忽略,并且只考虑 packages
配置属性。
使用 packages
配置属性很简单
quarkus.hibernate-orm.schema-management.strategy=drop-and-create
quarkus.hibernate-orm.packages=org.acme.model.defaultpu
quarkus.hibernate-orm."users".schema-management.strategy=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.shared
和 org.acme.model.user
包下的所有模型类都将附加到 users
持久化单元。
也支持将给定的模型类附加到多个持久化单元。
模型类需要一致地添加到给定的持久化单元。这意味着给定实体的所有依赖模型类(映射的超类、可嵌入类等)都必须附加到持久化单元。由于我们在包级别处理持久化单元,因此这应该足够简单。 |
Panache 实体只能附加到一个持久化单元。 对于附加到多个持久化单元的实体,您不能使用 Panache。但是,您可以混合使用这两种方法,并在需要多个持久化单元时混合使用 Panache 实体和传统实体。 如果您有此用例,并且有关于如何实现它的聪明想法,而不会弄乱简化的 Panache 方法,请通过quarkus-dev 邮件列表与我们联系。 |
将模型类附加到持久化单元的第二种方法是使用包级别的 @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 注解。 |
在此情况下,我们仅支持在包级别定义模型类的 |
请注意,与我们使用配置属性的方式类似,我们不仅考虑了注解的包,还考虑了其所有子包。
CDI 集成
如果您熟悉在 Quarkus 中使用 Hibernate ORM,您可能已经使用 CDI 注入了 EntityManager
@Inject
EntityManager entityManager;
这将注入默认持久化单元的 EntityManager
。
注入命名持久化单元(例如我们的 users
)的 EntityManager
非常简单,如下所示
@Inject
@PersistenceUnit("users") (1)
EntityManager entityManager;
1 | 这里,我们再次使用相同的 @io.quarkus.hibernate.orm.PersistenceUnit 注解。 |
使用相同的机制可以注入命名持久化单元的 EntityManagerFactory
@Inject
@PersistenceUnit("users")
EntityManagerFactory entityManagerFactory;
除了 EntityManager
和 EntityManagerFactory
之外,Quarkus 还支持注入以下 JPA/Hibernate 组件
@Inject
CriteriaBuilder criteriaBuilder;
@Inject
HibernateCriteriaBuilder hibernateCriteriaBuilder;
@Inject
Metamodel metamodel;
@Inject
jakarta.persistence.Cache cache;
@Inject
org.hibernate.Cache cache;
@Inject
jakarta.persistence.PersistenceUnitUtil persistenceUnitUtil;
@Inject
jakarta.persistence.SchemaManager schemaManager;
@Inject
org.hibernate.relational.SchemaManager schemaManager;
这些组件也可以使用特定的持久化单元限定符进行注入
@Inject
@PersistenceUnit("users")
CriteriaBuilder criteriaBuilder;
激活/停用持久化单元
如果在构建时配置了持久化单元,则默认情况下它在运行时是激活的,即 Quarkus 将在应用程序启动时启动相应的 Hibernate ORM SessionFactory
。
要在运行时停用持久化单元,请将 quarkus.hibernate-orm[.optional name].active
设置为 false
。如果持久化单元未激活
-
SessionFactory
将不会在应用程序启动期间启动。 -
访问
EntityManagerFactory
/EntityManager
或SessionFactory
/Session
将导致抛出异常。
这尤其有用,当您希望应用程序能够在运行时使用预定数据集中的一个数据源。
例如,使用以下配置
quarkus.hibernate-orm."pg".packages=org.acme.model.shared
quarkus.hibernate-orm."pg".datasource=pg
quarkus.hibernate-orm."pg".schema-management.strategy=drop-and-create
quarkus.hibernate-orm."pg".active=false
quarkus.datasource."pg".db-kind=h2
quarkus.datasource."pg".active=false
%prod.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".schema-management.strategy=drop-and-create
quarkus.hibernate-orm."oracle".active=false
quarkus.datasource."oracle".db-kind=oracle
quarkus.datasource."oracle".active=false
%prod.quarkus.datasource."oracle".jdbc.url=jdbc:oracle:///your_database
在运行时设置 quarkus.hibernate-orm."pg".active=true
和 quarkus.datasource."pg".active=true
将仅使 PostgreSQL 持久化单元和数据源可用,而在运行时设置 quarkus.hibernate-orm."oracle".active=true
和 quarkus.datasource."oracle".active=true
将仅使 Oracle 持久化单元和数据源可用。
可以使用自定义配置配置文件来简化此类设置。通过将以下特定于配置文件的配置添加到上述配置中,您可以通过设置
|
有了这样的设置,您需要注意只访问*活动的*持久化单元。为此,您可以为默认 Session
定义一个CDI bean producer,将其实例重定向到当前活动的命名 Session
,以便可以直接注入它,如下所示
public class MyProducer {
@Inject
@DataSource("pg")
InjectableInstance<AgroalDataSource> pgDataSourceBean; (1)
@Inject
@DataSource("oracle")
InjectableInstance<AgroalDataSource> oracleDataSourceBean;
@Inject
@PersistenceUnit("pg")
Session pgSessionBean;
@Inject
@PersistenceUnit("oracle")
Session oracleSessionBean;
@Produces (2)
@ApplicationScoped
public Session session() {
if (pgDataSourceBean.getHandle().getBean().isActive()) { (3)
return pgSessionBean;
} else if (oracleDataSourceBean.getHandle().getBean().isActive()) { (3)
return oracleSessionBean;
} else {
throw new RuntimeException("No active datasource!");
}
}
}
@ApplicationScoped
public class MyConsumer {
@Inject
Session session; (4)
public void doSomething() {
// .. just use the injected session ...
}
}
1 | 不要直接注入 DataSource 或 AgroalDatasource ,因为这会导致启动失败(无法注入非活动 bean)。相反,注入 InjectableInstance<DataSource> 或 InjectableInstance<AgroalDataSource> 。 |
2 | 声明一个 CDI producer 方法,该方法将默认会话定义为 PostgreSQL 或 Oracle,具体取决于哪个是活动的。 |
3 | 在检索相应的会话之前,检查数据源 bean 是否处于活动状态。 |
4 | 这将注入(唯一)活动的会话。 |
使用 persistence.xml
设置和配置 Hibernate ORM
要设置和配置 Hibernate ORM,推荐使用 application.properties
,但您也可以选择使用 META-INF/persistence.xml
文件。这主要用于将现有代码迁移到 Quarkus。
使用
如果您希望忽略类路径中的
|
您的 pom.xml
依赖项以及您的 Java 代码将与之前的示例相同。唯一的区别是您将在 META-INF/persistence.xml
中指定 Hibernate ORM 配置
<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,因此在这种情况下,适当的参考是hibernate.org 上的文档。
请记住,这些属性名与 Quarkus application.properties
中使用的属性名不同,默认值也不会相同。
XML 映射
Quarkus 中的 Hibernate ORM 支持 XML 映射。您可以添加遵循orm.xml
格式(Jakarta Persistence)或hbm.xml
格式(Hibernate ORM 特有,已弃用)的映射文件
-
通过(构建时)
quarkus.hibernate-orm.mapping-files
属性在application.properties
中。 -
通过
<mapping-file>
元素在persistence.xml
中。
XML 映射文件在构建时解析。
如果 如果这不是您想要的,请使用 |
在外部项目或 jar 中定义实体
Quarkus 中的 Hibernate ORM 依赖于编译时字节码增强您的实体。如果您在构建 Quarkus 应用程序的同一个项目中定义实体,一切都会正常工作。
如果实体来自外部项目或 jar,您可以通过添加一个空的 META-INF/beans.xml
文件来确保您的 jar 被视为 Quarkus 应用程序库。
这将允许 Quarkus 索引和增强您的实体,就像它们位于当前项目中一样。
开发模式下的 Hibernate ORM
Quarkus 开发模式对于混合前端或服务和数据库访问的应用程序非常有用。
有几种常用方法可以充分利用它。
第一个选择是将 quarkus.hibernate-orm.schema-management.strategy=drop-and-create
与 import.sql
结合使用。
这样,对于您应用程序的每一次更改,特别是对您的实体的更改,数据库模式都将被正确地重新创建,并且您的数据夹具(存储在 import.sql
中)将用于从头开始重新填充它。这对于完美控制您的环境是最好的,并且与 Quarkus 实时重新加载模式配合得很好:您的实体更改或 import.sql
的任何更改都会立即被拾取,并且在不重启应用程序的情况下更新模式!
默认情况下,在 |
第二个方法是使用 quarkus.hibernate-orm.schema-management.strategy=update
。此方法最适合您进行许多实体更改,但仍需要处理生产数据的副本,或者如果您想重现基于特定数据库条目的错误。update
是 Hibernate ORM 的尽力而为,在特定情况下会失败,包括更改可能导致数据丢失的数据库结构。例如,如果您更改违反外键约束的结构,Hibernate ORM 可能不得不放弃。但对于开发来说,这些限制是可以接受的。
第三种方法是使用 quarkus.hibernate-orm.schema-management.strategy=none
。此方法最适合您处理生产数据的副本,但又想完全控制模式演变。或者,如果您使用数据库模式迁移工具,如Flyway 或Liquibase。
使用此方法进行实体更改时,请确保相应地调整数据库模式;您也可以使用 validate
让 Hibernate 验证模式是否符合其预期。
不要在生产环境中使用 quarkus.hibernate-orm.schema-management.strategy drop-and-create 和 update 。 |
当与 Quarkus 配置配置文件结合使用时,这些方法会变得非常强大。您可以定义不同的配置配置文件,根据您的环境选择不同的行为。这很棒,因为您可以定义 Hibernate ORM 属性的不同组合,以匹配您当前所需的开发风格。
%dev.quarkus.hibernate-orm.schema-management.strategy = drop-and-create
%dev.quarkus.hibernate-orm.sql-load-script = import-dev.sql
%dev-with-data.quarkus.hibernate-orm.schema-management.strategy = update
%dev-with-data.quarkus.hibernate-orm.sql-load-script = no-file
%prod.quarkus.hibernate-orm.schema-management.strategy = none
%prod.quarkus.hibernate-orm.sql-load-script = no-file
您可以使用自定义配置文件启动开发模式
quarkus dev -Dquarkus.profile=dev-with-data
./mvnw quarkus:dev -Dquarkus.profile=dev-with-data
./gradlew --console=plain quarkusDev -Dquarkus.profile=dev-with-data
生产模式下的 Hibernate ORM
Quarkus 带有默认配置文件(dev
、test
和 prod
)。您可以添加自己的自定义配置文件来描述各种环境(staging
、prod-us
等)。
Hibernate ORM Quarkus 扩展在 dev 和 test 模式下的默认配置与其他环境不同。
-
quarkus.hibernate-orm.sql-load-script
除dev
和test
配置文件外,均设置为no-file
。
您可以在 application.properties
中显式覆盖它(例如,%prod.quarkus.hibernate-orm.sql-load-script = import.sql
),但我们希望您避免在 prod 中意外覆盖数据库 :)
说起来,请确保不要在生产环境中删除数据库模式!在您的属性文件中添加以下内容。
%prod.quarkus.hibernate-orm.schema-management.strategy = none
%prod.quarkus.hibernate-orm.sql-load-script = no-file
自动转换为 Flyway 管理模式
如果您在开发模式下安装了Flyway 扩展,Quarkus 提供了一种简单的方法来使用 Hibernate ORM 自动生成的模式来初始化您的 Flyway 配置。这旨在简化从早期开发阶段的迁移,在早期开发阶段,Hibernate 可用于快速设置模式,而在生产阶段,Flyway 用于管理模式更改。
要使用此功能,只需打开 Dev UI,当安装了 quarkus-flyway
扩展时,然后在 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
设置,以便在 dev/test 模式下重新加载后清理 DB
此按钮仅用于方便地让您快速开始使用 Flyway,如何管理生产环境中的数据库模式由您决定。特别是 migrate-at-start 设置可能不适用于所有环境。 |
缓存
当 Hibernate ORM 二级缓存启用时,频繁读取相同实体的应用程序的性能会得到提高。
实体缓存
要启用二级缓存,请使用 @jakarta.persistence.Cacheable
注解您要缓存的实体
@Entity
@Cacheable
public class Country {
int dialInCode;
// ...
}
当实体被注解为 @Cacheable
时,其所有字段值都会被缓存,但集合和与其他实体的关系除外。
这意味着可以在不查询数据库的情况下加载实体,但请注意,这可能意味着加载的实体可能无法反映数据库中的最新更改。
集合和关系缓存
集合和关系需要单独注解才能进行缓存;在这种情况下,应使用 Hibernate 特有的 @org.hibernate.annotations.Cache
,这还需要指定 CacheConcurrencyStrategy
package org.acme;
@Entity
@Cacheable
public class Country {
// ...
@OneToMany
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
List<City> cities;
// ...
}
查询缓存
查询也可以从二级缓存中受益。缓存的查询结果可以立即返回给调用者,避免在数据库上运行查询。
请注意,这可能意味着结果可能无法反映最新更改。
要缓存查询,请在 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 中集成并默认启用,因此只需设置哪些内容可以安全缓存即可。
缓存区域调整
缓存将数据存储在单独的区域中,以隔离不同部分的数据;这些区域被分配了一个名称,这对于独立配置每个区域或监控其统计信息很有用。
默认情况下,实体缓存在以其完全限定名命名的区域中,例如 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 中提供的缓存技术目前相当粗糙和有限。
团队认为,最好先拥有*一些*缓存功能,而不是什么都没有;您可以预期在未来的版本中会集成更好的缓存解决方案,并且非常欢迎在此领域提供的任何帮助和反馈。
这些缓存保存在本地,因此当其他应用程序对持久化存储进行更改时,它们不会失效或更新。 此外,当同一应用程序运行多个副本时(例如在 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
;您只需将其添加到项目中即可开始使用。
<!-- Add the Hibernate Envers extension -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-envers</artifactId>
</dependency>
此时,该扩展不公开其他配置属性。
有关 Hibernate Envers 的更多信息,请参阅hibernate.org/orm/envers/。
指标
Micrometer 或 SmallRye Metrics 能够公开 Hibernate ORM 在运行时收集的指标。要启用 Hibernate 指标在 /q/metrics
端点上的公开,请确保您的项目依赖于指标扩展,并将配置属性 quarkus.hibernate-orm.metrics.enabled
设置为 true
。使用SmallRye Metrics时,指标将在 vendor
作用域下可用。
局限性和其他您应该知道的事情
Quarkus 不会修改它使用的库;此规则也适用于 Hibernate ORM:使用此扩展时,您的体验与使用原始库大致相同。
但是,虽然它们共享相同的代码,但 Quarkus 会自动配置某些组件,并为某些扩展点注入自定义实现;这应该是透明且有用的,但如果您是 Hibernate 的专家,您可能想知道正在做什么。
自动构建时增强
Hibernate ORM 可以使用构建时增强的实体;通常这不是必需的,但它很有用,并且可以提高应用程序的性能。
通常,您需要调整构建脚本以包含 Hibernate Enhancement 插件;在 Quarkus 中,这不是必需的,因为增强步骤已集成到 Quarkus 应用程序的构建和分析中。
由于使用了增强,因此目前不支持在实体上使用 此限制将来可能会被移除。 |
自动集成
- 事务管理器集成
-
您无需设置此项,Quarkus 会自动注入 Narayana 事务管理器的引用。依赖项作为 Hibernate ORM 扩展的传递依赖项自动包含。所有配置都是可选的;有关更多详细信息,请参阅在 Quarkus 中使用事务。
- 连接池
-
您也不需要选择一个。Quarkus 会自动包含 Agroal 连接池;如以上示例所示配置您的数据源,它将设置 Hibernate ORM 以使用 Agroal。有关此连接池的更多详细信息,请参阅Quarkus - Datasources。
- 二级缓存
-
如缓存部分中所述,您无需选择实现。基于Infinispan 和Caffeine 的技术的合适实现作为 Hibernate ORM 扩展的传递依赖项包含,并在构建期间自动集成。
限制
- XML 映射与类路径中的重复文件
-
XML 映射文件的路径应该是唯一的。
实际上,在非常特定的场景中,类路径中只能出现重复的 XML 映射文件。例如,如果两个 JAR 文件包含
META-INF/orm.xml
文件(路径完全相同,但在不同的 JAR 文件中),那么映射文件路径META-INF/orm.xml
只能从位于*与META-INF/orm.xml
文件相同的 JAR* 中的persistence.xml
引用。 - JMX
-
JMX 管理 Bean 在 GraalVM 原生映像中不起作用;因此,当编译为原生映像时,Hibernate 将统计信息和管理操作注册到 JMX Bean 的功能将被禁用。此限制可能是永久性的,因为它不是原生映像支持 JMX 的目标。所有这些指标都可以通过其他方式访问。
- JACC 集成
-
Hibernate ORM 与 JACC 集成的能力在构建 GraalVM 原生映像时被禁用,因为 JACC 在原生模式下不可用,也无用。
- 将会话绑定到 ThreadLocal 上下文
-
不可能使用 Hibernate ORM 的
ThreadLocalSessionContext
助手,因为不支持它。由于 Quarkus 提供开箱即用的 CDI 支持,因此注入或编程 CDI 查找是更好的方法。此功能也与响应式组件和更现代的上下文传播技术集成不佳,这使我们相信此遗留功能没有未来。如果您非常需要将其绑定到 ThreadLocal,则在您自己的代码中实现它应该很简单。 - JNDI
-
JNDI 技术通常在其他运行时中用于集成不同的组件。一个常见的用例是 Java Enterprise 服务器将 TransactionManager 和 Datasource 组件绑定到一个名称,然后配置 Hibernate ORM 以按名称查找这些组件。但在 Quarkus 中,这种情况不适用,因为组件是直接注入的,这使得 JNDI 支持成为不必要的遗留。为避免意外使用 JNDI,Hibernate ORM 扩展对 Quarkus 的 JNDI 支持已被完全禁用。这既是为了安全考虑,也是为了优化。
使用 Panache 简化 Hibernate ORM
Hibernate ORM with Panache 扩展通过提供 ActiveRecord 风格的实体(和存储库)来简化 Hibernate ORM 的使用,并专注于使您的实体在 Quarkus 中编写起来简单有趣。
配置您的数据源
数据源配置非常简单,但已在不同的指南中介绍,因为它在技术上是由 Quarkus 的 Agroal 连接池扩展实现的。
请转到Quarkus - Datasources 以获取所有详细信息。
多租户
“总的来说,多租户这个词被应用于软件开发,以表示一种架构,在这种架构中,应用程序的单个运行实例同时服务于多个客户(租户)。这在 SaaS 解决方案中非常普遍。隔离与各个租户相关的信息(数据、自定义等)是这些系统中的一个特定挑战。这包括存储在数据库中的每个租户拥有的数据”(Hibernate 用户指南)。
要实际查看多租户,您可以查看hibernate-orm-multi-tenancy-schema-quickstart 或hibernate-orm-multi-tenancy-database-quickstart。
编写应用程序
让我们开始实现 /{tenant}
端点。从下面的源代码可以看出,它只是一个常规的 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]);
}
}
为了从传入请求解析租户并将其映射到特定的租户配置,您需要为 io.quarkus.hibernate.orm.runtime.tenant.TenantResolver
接口创建一个实现。
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 | 使用 @PersistenceUnitExtension 限定符注解 TenantResolver 实现,以告知 Quarkus 它应该在默认持久化单元中使用。对于命名持久化单元,请使用 |
2 | 该 bean 被设置为 @RequestScoped ,因为租户解析取决于传入的请求。 |
从上面的实现可以看出,租户是从请求路径解析的,因此在无法推断出租户的情况下,将返回默认租户标识符。
如果您还使用OIDC 多租户,并且 OIDC 和 Hibernate ORM 租户 ID 相同,您可以从
|
配置应用程序
通常,无法在多租户设置中同时使用 Hibernate ORM 数据库生成功能。因此,您必须禁用它,并且需要确保为每个模式创建表。以下设置将使用Flyway 扩展来实现此目标。
SCHEMA 方法
所有租户将使用相同的数据源,并且必须在每个数据源中为每个租户创建一个模式。
某些数据库(如 MariaDB/MySQL)默认不支持数据库模式。在这种情况下,您可以:1. 配置 JDBC 驱动程序以支持模式。使用 quarkus.datasource.jdbc.additional-jdbc-properties."databaseTerm"=SCHEMA 或 quarkus.datasource."datasource-name".jdbc.additional-jdbc-properties."databaseTerm"=SCHEMA 用于MySQL Connector/J。使用 quarkus.datasource.jdbc.additional-jdbc-properties."useCatalogTerm"=SCHEMA 或 quarkus.datasource."datasource-name".jdbc.additional-jdbc-properties."useCatalogTerm"=SCHEMA 用于MariaDB Connector/J。 |
并 2. 回退到数据库方法。
quarkus.hibernate-orm.schema-management.strategy=none (1)
quarkus.hibernate-orm.multitenant=SCHEMA (2)
quarkus.datasource.db-kind=postgresql (3)
quarkus.flyway.schemas=base,mycompany (4)
quarkus.flyway.locations=classpath:schema
quarkus.flyway.migrate-at-start=true
%prod.quarkus.datasource.username=quarkus_test
%prod.quarkus.datasource.password=quarkus_test
%prod.quarkus.datasource.jdbc.url=jdbc:postgresql://:5432/quarkus_test
1 | 禁用模式生成,因为它不受 Hibernate ORM 对模式多租户的支持。我们稍后将使用 Flyway。 |
2 | 启用模式多租户。 我们在这里使用默认数据源,但如果需要,可以通过此处的说明使用命名数据源。 |
3 | 配置数据源. |
4 | 配置Flyway进行数据库初始化,因为 Hibernate ORM 的模式生成在此情况下不受支持。 |
这是 Flyway SQL(V1.0.0__create_fruits.sql
)的示例,将在配置的文件夹 src/main/resources/schema
中创建。
CREATE SEQUENCE base.fruit_seq INCREMENT BY 50; -- 50 is quarkus default
CREATE TABLE base.fruit
(
id INT,
name VARCHAR(40)
);
INSERT INTO base.fruit(id, name) VALUES (1, 'Cherry');
INSERT INTO base.fruit(id, name) VALUES (2, 'Apple');
INSERT INTO base.fruit(id, name) VALUES (3, 'Banana');
ALTER SEQUENCE base.fruit_seq RESTART WITH 4;
CREATE SEQUENCE mycompany.fruit_seq INCREMENT BY 50; -- 50 is quarkus default
CREATE TABLE mycompany.fruit
(
id INT,
name VARCHAR(40)
);
INSERT INTO mycompany.fruit(id, name) VALUES (1, 'Avocado');
INSERT INTO mycompany.fruit(id, name) VALUES (2, 'Apricots');
INSERT INTO mycompany.fruit(id, name) VALUES (3, 'Blackberries');
ALTER SEQUENCE mycompany.fruit_seq RESTART WITH 4;
DATABASE 方法
对于每个租户,您需要创建一个命名数据源,其标识符与 TenantResolver
返回的标识符相同。
通过此方法,同一持久化单元使用的所有数据源都假定指向相同供应商(相同 不会检测到不匹配,并可能导致不可预测的行为。 数据源列表在构建时定义,因此通过此方法,租户的*列表*在*构建时固定*。如果租户列表需要在运行时更改,您必须以编程方式解析租户连接。 |
quarkus.hibernate-orm.schema-management.strategy=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.flyway.base.locations=classpath:database/base (5)
quarkus.flyway.base.migrate-at-start=true
%prod.quarkus.datasource.base.username=base
%prod.quarkus.datasource.base.password=base
%prod.quarkus.datasource.base.jdbc.url=jdbc:postgresql://:5432/base
# Tenant 'mycompany'
quarkus.datasource.mycompany.db-kind=postgresql (6)
quarkus.flyway.mycompany.locations=classpath:database/mycompany (7)
quarkus.flyway.mycompany.migrate-at-start=true
%prod.quarkus.datasource.mycompany.username=mycompany
%prod.quarkus.datasource.mycompany.password=mycompany
%prod.quarkus.datasource.mycompany.jdbc.url=jdbc:postgresql://:5433/mycompany
1 | 禁用模式生成,因为它不受 Hibernate ORM 对数据库多租户的支持。我们稍后将使用 Flyway。 |
2 | 启用数据库多租户。 |
3 | 为持久化单元选择数据源。 这仅是为了允许 Quarkus 确定要使用的 Hibernate ORM 方言;有关详细信息,请参阅本节。 |
4 | 为租户 base 配置数据源。 |
5 | 为租户 base 配置Flyway进行数据库初始化,因为 Hibernate ORM 的模式生成在此情况下不受支持。 |
6 | 为另一个租户配置数据源。 可能还有更多租户,但我们在这里仅限于两个。 |
7 | 为租户 mycompany 配置Flyway进行数据库初始化,因为 Hibernate ORM 的模式生成在此情况下不受支持。 |
以下是在配置的文件夹 src/main/resources/database
中创建的 Flyway SQL 文件的示例。
租户 base
的模式(src/main/resources/database/base/V1.0.0__create_fruits.sql
)
CREATE SEQUENCE fruit_seq INCREMENT BY 50; -- 50 is quarkus default
CREATE TABLE fruit
(
id INT,
name VARCHAR(40)
);
INSERT INTO fruit(id, name) VALUES (1, 'Cherry');
INSERT INTO fruit(id, name) VALUES (2, 'Apple');
INSERT INTO fruit(id, name) VALUES (3, 'Banana');
ALTER SEQUENCE fruit_seq RESTART WITH 4;
租户 mycompany
的模式(src/main/resources/database/mycompany/V1.0.0__create_fruits.sql
)
CREATE SEQUENCE fruit_seq INCREMENT BY 50; -- 50 is quarkus default
CREATE TABLE fruit
(
id INT,
name VARCHAR(40)
);
INSERT INTO fruit(id, name) VALUES (1, 'Avocado');
INSERT INTO fruit(id, name) VALUES (2, 'Apricots');
INSERT INTO fruit(id, name) VALUES (3, 'Blackberries');
ALTER SEQUENCE fruit_seq RESTART WITH 4;
DISCRIMINATOR 方法
默认数据源将用于所有租户。所有定义了用 @TenantId
注解的字段的实体都将自动填充该字段,并在查询中自动进行过滤。
quarkus.hibernate-orm.multitenant=DISCRIMINATOR (1)
quarkus.datasource.db-kind=postgresql (2)
%prod.quarkus.datasource.username=quarkus_test
%prod.quarkus.datasource.password=quarkus_test
%prod.quarkus.datasource.jdbc.url=jdbc:postgresql://:5432/quarkus_test
1 | 启用鉴别器多租户。 |
2 | 配置数据源. |
以编程方式解析租户连接
如果您需要更动态的配置来支持不同的租户,并且不想在配置文件中出现多个条目,您可以使用 io.quarkus.hibernate.orm.runtime.tenant.TenantConnectionResolver
接口来实现您的连接检索逻辑。创建实现此接口的应用程序作用域 bean,并使用 @PersistenceUnitExtension
(或对于命名持久化单元使用 @PersistenceUnitExtension("nameOfYourPU")
)进行注解,这将替换 Quarkus 当前的默认实现 io.quarkus.hibernate.orm.runtime.tenant.DataSourceTenantConnectionResolver
。您的自定义连接解析器将允许例如在运行时根据数据库中的租户信息来创建每个租户的连接。
自动集成*不会*与以编程方式创建的 |
以下是使用标准 Quarkus 技术(Agroal 用于连接池,Narayana 用于事务)的 TenantConnectionResolver
实现的示例。
@ApplicationScoped
@PersistenceUnitExtension
public class ExampleTenantConnectionResolver implements TenantConnectionResolver {
private final jakarta.transaction.TransactionManager transactionManager;
private final TransactionSynchronizationRegistry transactionSynchronizationRegistry;
public ExampleTenantConnectionResolver(
TransactionManager transactionManager,
TransactionSynchronizationRegistry transactionSynchronizationRegistry) {
this.transactionManager = transactionManager;
this.transactionSynchronizationRegistry = transactionSynchronizationRegistry;
}
@Override
public ConnectionProvider resolve(String tenantId) {
// Use your own ConnectionProvider implementation here
return new YourOwnCustomConnectionProviderImpl(createDatasource(tenantId));
}
private AgroalDataSource createDatasource(String tenantId) {
try {
final var txIntegration = new NarayanaTransactionIntegration(
transactionManager, transactionSynchronizationRegistry, null, false, null);
// Fetch JDBC URL, username, password & other values from a per-tenant dynamic source
final var dataSourceConfig = new AgroalDataSourceConfigurationSupplier()
.connectionPoolConfiguration(pc -> pc.initialSize(2)
.maxSize(10)
.minSize(2)
.maxLifetime(Duration.of(5, ChronoUnit.MINUTES))
.acquisitionTimeout(Duration.of(30, ChronoUnit.SECONDS))
.transactionIntegration(txIntegration)
.connectionFactoryConfiguration(
cf -> cf.jdbcUrl("jdbc:postgresql://postgres:5432/" + tenantId)
.credential(new NamePrincipal(username))
.credential(new SimplePassword(password))));
return AgroalDataSource.from(dataSourceConfig.get());
} catch (SQLException ex) {
throw new IllegalStateException(
"Failed to create a new data source based on the existing datasource configuration", ex);
}
}
}
拦截器
您可以通过简单地定义一个具有适当限定符的 CDI bean 来将 org.hibernate.Interceptor
分配给您的 SessionFactory
@PersistenceUnitExtension (1)
public static class MyInterceptor implements Interceptor, Serializable { (2)
@Override
public boolean onLoad(Object entity, Object id, Object[] state, (3)
String[] propertyNames, Type[] types) {
// ...
return false;
}
}
1 | 使用 @PersistenceUnitExtension 限定符注解拦截器实现,以告知 Quarkus 它应该在默认持久化单元中使用。对于命名持久化单元,请使用 |
2 | 根据需要实现 org.hibernate.Interceptor 的方法。 |
默认情况下,使用 为了每个实体管理器实例创建一个拦截器,请将您的 bean 注释为 |
由于 Hibernate ORM 本身的限制, |
语句拦截器
您可以通过简单地定义一个具有适当限定符的 CDI bean 来将 org.hibernate.engine.jdbc.spi.StatementInspector
分配给您的 SessionFactory
。
@PersistenceUnitExtension (1)
public class MyStatementInspector implements StatementInspector { (2)
@Override
public String inspect(String sql) {
// ...
return sql;
}
}
1 | 将语句拦截器实现注释为 @PersistenceUnitExtension 限定符,以告知 Quarkus 它应该在默认持久性单元中使用。对于命名持久化单元,请使用 |
2 | 实现 org.hibernate.engine.jdbc.spi.StatementInspector 。 |
自定义 JSON/XML 序列化/反序列化
默认情况下,Quarkus 将尝试根据可用的扩展自动配置格式映射器。当 Jackson (或 JSON-B) 可用时,全局配置的 ObjectMapper
(或 Jsonb
) 将用于序列化/反序列化操作。如果 Jackson 和 JSON-B 同时可用,Jackson 将优先。
Hibernate ORM 中的 JSON 和 XML 序列化/反序列化可以通过实现 org.hibernate.type.format.FormatMapper
并使用适当的限定符注释实现来定制。
import io.quarkus.hibernate.orm.JsonFormat;
import org.hibernate.type.format.FormatMapper;
@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 它应该在默认持久性单元中使用。对于命名持久化单元,请使用 |
||
3 | 实现 org.hibernate.type.format.FormatMapper 。 |
对于自定义 XML 格式映射器,必须应用不同的 CDI 限定符。
import io.quarkus.hibernate.orm.XmlFormat;
import org.hibernate.type.format.FormatMapper;
@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 它应该在默认持久性单元中使用。对于命名持久化单元,请使用 |
3 | 实现 org.hibernate.type.format.FormatMapper 。 |
格式映射器必须同时具有 为同一持久性单元注册多个 JSON (或 XML) 格式映射器将由于歧义而导致异常。 |
验证模式和 Hibernate Validator 集成
Hibernate Validator 与 Hibernate ORM 的集成提供了以下功能:
-
在生命周期事件上执行实体验证
-
将约束信息从实体应用于 DDL
从 Quarkus 的角度来看,这由 quarkus.hibernate-orm.validation.mode
配置属性 控制。可用的验证模式是:
-
auto
— 默认选项;当应用程序使用quarkus-hibernate-validator
时,其行为与同时启用callback
和ddl
相同,否则与none
相同。 -
callback
— Hibernate Validator 将执行生命周期事件验证。 -
ddl
— Hibernate Validator 约束将考虑用于 DDL 操作。 -
none
— 将禁用 Hibernate Validator 集成。
虽然所有具有相应 Jakarta Validation 规则的约束将在 callback
验证期间应用,但在 ddl
模式下,只有一部分约束用于影响 DDL。在 Hibernate Validator 文档中,您可以找到 可用约束列表 及其对 DDL 生成的影响。
让我们考虑一个带有应用于其属性的约束的简单实体。
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
@Entity
public class MyEntity {
@Id
@GeneratedValue
public long id;
@NotNull
@NotEmpty
@Size(max = 50)
public String name;
public String value;
}
当启用 ddl
模式时,预期的结果模式将具有以下约束:
create table myentity
(
id bigint not null primary key,
name varchar(50) not null, (1)
value varchar(255), (2)
);
1 | name 列由于 @NotNull 约束而具有 not null 约束,并且由于 @Size(max=50) 约束,值的长度限制为 50 。 |
2 | 由于实体中的 value 属性没有任何约束,DDL 中没有其他约束,并且 255 的长度限制来自默认的 jakarta.persistence.Column#lenght() 。 |
使用 callback
模式,预期会抛出 jakarta.validation.ConstraintViolationException
。
try {
MyEntity entity = new MyEntity();
entity.setName(veryLongName);
em.persist(entity);
em.flush();
} catch (ConstraintViolationException exception) {
// handle the constraint violations somehow
}
由于 Quarkus 具有 jakarta.validation.ConstraintViolationException
的内置异常映射器,显式处理这些异常可能没有必要。有关更多详细信息,请参阅 Hibernate Validator 指南的 REST 端点验证 部分。
静态元模型和 Jakarta Data
Hibernate ORM 的静态元模型和 Jakarta Data 功能都通过 hibernate-processor
注释处理器在 Quarkus 中可用。由于它是一个注释处理器,您必须在构建工具中对其进行相应配置。
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-processor</artifactId>
<!-- Note, no artifact version is required, it's managed by Quarkus. -->
</path>
<!-- other processors that may be required by your app -->
</annotationProcessorPaths>
<!-- Other compiler plugin configuration options -->
</configuration>
</plugin>
// Enforce the version management of your annotation processor dependencies,
// so that there's no need to define an explicit version of the hibernate-processor
annotationProcessor enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}")
annotationProcessor 'org.hibernate.orm:hibernate-processor'
静态元模型
生成的静态元模型允许以类型安全的方式构建查询。让我们考虑一个简单的实体。
@Entity
public class MyEntity {
@Id
@GeneratedValue
public Integer id;
@Column(unique = true)
public String name;
}
使用静态元模型创建的查询可能如下所示:
var builder = session.getCriteriaBuilder();
var criteria = builder.createQuery(MyEntity.class);
var e = criteria.from(MyEntity_.class);
criteria.where(e.get(MyEntity_.name).equalTo(name));
var query = session.createQuery(criteria);
var result = query.list();
有关静态元模型的更详细概述,请参阅 Jakarta Persistence 规范。
Jakarta Data
Jakarta Data 除了需要 hibernate-processor
注释处理器到位之外,还需要添加一个额外的依赖项。
<dependency>
<groupId>jakarta.data</groupId>
<artifactId>jakarta.data-api</artifactId>
</dependency>
implementation 'jakarta.data:jakarta.data-api'
有了这个依赖项和注释处理器,您可以像这样简单地创建您的存储库:
@Repository
public interface MyRepository extends CrudRepository<MyEntity, Integer> { (1)
@Query("select e from MyEntity e where e.name like :name") (2)
List<MyEntity> findByName(String name);
@Delete (3)
void delete(String name);
}
-
为了跳过 CRUD 操作的样板定义,我们可以使用可用的接口之一 (例如
CrudRepository
或BasicRepository
)。 -
通过向
@Query
注释提供查询字符串,添加带有参数的自定义查询非常简单。 -
如果 Jakarta Data 接口的基本 CRUD 操作不够,我们可以始终添加一个自定义操作,在这种情况下,是一个删除操作,它按名称删除 `MyEntity`。
然后,存储库可以像任何其他 bean 一样使用:
public class MyEntityResource {
@Inject
MyRepository repository;
@POST
@Transactional
public void create(MyEntity entity) {
repository.insert(entity);
}
// ...
}
请参阅相应的 Hibernate Data Repositories 和 Jakarta Data 指南,了解它们还提供哪些功能。