前言
Hibernate 是使用 Java 和其他 JVM 语言编写的程序的对象/关系映射 (ORM) 解决方案。
虽然使用 Hibernate 不需要有扎实的 SQL 背景,但对它的概念有一个基本的了解很有用 - 尤其是数据建模的原理。理解事务和工作单元等设计模式的基础知识也很重要。
1. 获取 Hibernate
Hibernate 被拆分为 org.hibernate.orm
组下的多个模块/工件。主体工件被命名为 hibernate-core
。
本指南使用 6.6.0.Final 作为演示的 Hibernate 版本。如果有需要,确保将此版本更改为您要使用的版本。 |
我们可以使用 Gradle
dependencies {
implementation "org.hibernate.orm:hibernate-core:6.6.0.Final"
}
或 Maven
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.6.0.Final</version>
</dependency>
1.1. Hibernate ORM 模块
声明对该工件的依赖关系,如前所述,Hibernate ORM 被拆分为多个模块,目的是根据正在使用的功能或未使用功能来隔离传递性依赖项。
|
核心对象/关系映射引擎 |
|
实体版本控制和审计 |
|
使用 GeoLatte 支持空间/GIS 数据类型 |
|
生成 JPA 兼容元模型的注释处理器,以及可选的 Hibernate 附加功能 |
|
支持数学向量类型和函数,这些对于 AI/ML 主题(例如向量相似性搜索和检索增强生成 (RAG))很有用 |
|
支持 Agroal 连接池 |
|
支持 C3P0 连接池 |
|
支持 HikariCP 连接池 |
|
支持 Vibur DBCP 连接池 |
|
支持 Proxool 连接池 |
|
支持 Universal Connection Pool 连接池 |
|
与 JCache 的集成,允许将任何兼容的实现用作二级缓存提供程序 |
|
实验性扩展,使其可以更轻松地将应用程序编译为 GraalVM 原生映像 |
|
与 Micrometer 指标集成 |
|
社区支持的其他 SQL 方言 |
|
用于测试 Hibernate ORM 功能的一系列 JUnit 扩展 |
1.2. 平台/BOM
Hibernate 还提供了一个平台(Maven 术语中的 BOM)模块,可用于将 Hibernate 模块的版本与其实现的版本保持一致。平台制品命名为 hibernate-platform
。
在 Gradle 中应用该平台
dependencies {
implementation platform "org.hibernate.orm:hibernate-platform:6.6.0.Final"
// use the versions from the platform
implementation "org.hibernate.orm:hibernate-core"
implementation "jakarta.transaction:jakarta.transaction-api"
}
请参阅 Gradle 文档,以了解应用平台的功能。
在 Maven 中应用平台 (BOM)
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
</dependency>
<dependency>
<groupId>jakarta.transaction</groupId>
<artifactId>jakarta.transaction-api</artifactId>
</dependency>
<dependencyManagement>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-platform</artifactId>
<version>6.6.0.Final</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencyManagement>
2. 使用原生 Hibernate API 的教程
-
使用
hibernate.properties
配置 Hibernate -
使用 本机引导 创建
SessionFactory
-
使用注解提供映射信息
-
使用
Session
持久化和查询数据
2.1. 通过属性文件进行配置
在此示例中,配置属性指定在名为 hibernate.properties
的文件中。
hibernate.properties
配置# Database connection settings
hibernate.connection.url=jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1
hibernate.connection.username=sa
hibernate.connection.password=
# Echo all executed SQL to console
hibernate.show_sql=true
hibernate.format_sql=true
hibernate.highlight_sql=true
# Automatically export the schema
hibernate.hbm2ddl.auto=create
以下属性指定 JDBC 连接信息
配置属性名称 | 目的 |
---|---|
|
数据库的 JDBC URL |
|
数据库凭据 |
这些教程使用 H2 嵌入式数据库,因此这些属性的值特定于在内存模式下运行 H2。 |
这些属性使在执行时以美观一致的格式将 SQL 记录到控制台
配置属性名称 | 目的 |
---|---|
|
如果为 |
|
如果为 |
|
如果为 |
在使用 Hibernate 开发持久性逻辑时,了解确切执行了哪些 SQL 非常重要。
2.2. 带注解的实体 Java 类
本教程的实体类是org.hibernate.tutorial.annotations.Event
。请注意,
-
该类对 property getter 和 setter 方法采用标准 JavaBean 命名约定,并对字段采用私有可见性。这是推荐的,但不是必需的。
-
无参数构造函数(也是一个 JavaBean 约定)对于所有持久化类来说是必需的。Hibernate 需要使用 Java 反射为你实例化对象。构造函数应该具有包私有或
public
可见性,以允许 Hibernate 为字段访问生成代理和优化代码。
用户指南的实体类型部分介绍了对实体类的完整需求集。 |
我们使用注释来将该类标识为实体,并将其映射到关系模式。
@Entity (1)
@Table(name = "Events") (2)
public class Event {
...
}
1 | @jakarta.persistence.Entity 将Event 类标记为实体。 |
2 | @jakarta.persistence.Table 显式指定映射表的名称。如果没有此注释,表名称将默认为Event 。 |
每个实体类都必须有一个标识符。
@Id (1)
@GeneratedValue (2)
private Long id;
1 | @jakarta.persistence.Id 将该字段标记为实体的标识符(主键)。 |
2 | @jakarta.persistence.GeneratedValue 指定这是一个合成 id,即系统生成的标识符(代理主键)。 |
实体的其他字段默认为持久。
private String title;
@Column(name = "eventDate") (1)
private LocalDateTime date;
1 | @jakarta.persistence.Column 显式指定映射列的名称。如果没有此注释,列名将默认为date ,这是某些数据库中的关键字。 |
2.3. 示例代码
类org.hibernate.tutorial.annotations.HibernateIllustrationTest
说明了使用 Hibernate 的本机 API,包括
-
Session
和SessionFactory
,以及 -
用于配置和引导的
org.hibernate.boot
。
有几种不同的方式来配置和启动 Hibernate,而这甚至不是最常见的方法。
本教程中的示例以 JUnit 测试的形式呈现。这种方法的好处是 setUp() 和 tearDown() 大致说明了当程序启动时如何创建org.hibernate.SessionFactory ,以及当程序终止时如何关闭 org.hibernate.SessionFactory 。 |
SessionFactory
protected void setUp() {
// A SessionFactory is set up once for an application!
final StandardServiceRegistry registry =
new StandardServiceRegistryBuilder()
.build(); (1) (2)
try {
sessionFactory =
new MetadataSources(registry) (3)
.addAnnotatedClass(Event.class) (4)
.buildMetadata() (5)
.buildSessionFactory(); (6)
}
catch (Exception e) {
// The registry would be destroyed by the SessionFactory, but we
// had trouble building the SessionFactory so destroy it manually.
StandardServiceRegistryBuilder.destroy(registry);
}
}
1 | setUp() 方法首先构建一个 StandardServiceRegistry 实例,它将配置信息纳入 org.hibernate.SessionFactory 可以使用的一组正在运行的 Services 中。 |
2 | 在这里,我们将所有配置信息都放入hibernate.properties ,因此没有什么值得关注的。 |
3 | 使用 StandardServiceRegistry ,我们创建MetadataSources ,它使我们能够向 Hibernate 说明我们的 domain 模型。 |
4 | 在这里,我们只有一个要注册的实体类。 |
5 | Metadata 的实例代表对应用程序领域模型完全且经过部分验证的视图。 |
6 | 引导过程的最后一步是为已配置服务和经过验证的领域模型构建一个 SessionFactory 。SessionFactory 是一个线程安全对象,实例化一次即可为整个应用程序服务。 |
SessionFactory
生成 Session
的实例。每个会话应被视为代表一个工作单元。
sessionFactory.inTransaction(session -> { (1)
session.persist(new Event("Our very first event!", now())); (2)
session.persist(new Event("A follow up event", now()));
});
1 | inTransaction() 方法创建一个会话并开始一个新事务。 |
2 | 这里我们创建两个新的 Event 对象并将它们传递给 Hibernate,调用 persist() 方法使这些实例持久化。Hibernate 负责为每个 Event 执行一个 INSERT 语句。 |
sessionFactory.inTransaction(session -> {
session.createSelectionQuery("from Event", Event.class) (1)
.getResultList() (2)
.forEach(event -> out.println("Event (" + event.getDate() + ") : " + event.getTitle()));
});
1 | 这里我们使用一个非常简单的Hibernate Query Language (HQL) 语句从数据库中加载所有现有的 Event 对象。 |
2 | Hibernate 生成并执行适当的 SELECT 语句,然后实例化并使用查询结果集中的数据填充 Event 对象。 |
2.4. 更进一步!
-
实际运行此示例以查看控制台中显示的 Hibernate 执行的 SQL。
-
重新配置示例以连接到您自己的持久性关系数据库。
-
添加一个与
Event
实体的关联,以对消息线程建模。
3. 使用 JPA 标准 API 的教程
-
使用
persistence.xml
配置 Hibernate -
引导 Jakarta Persistence
EntityManagerFactory
-
使用注解提供映射信息
-
使用
EntityManager
来持久化和查询数据
3.1. persistence.xml
JPA 定义了一个不同的引导过程,以及一个名为 persistence.xml
的标准配置文件格式。在 Java™ SE 环境中,持久性提供程序(Hibernate)需要在类路径的 META-INF/persistence.xml
路径中找到每个 JPA 配置文件。
persistence.xml
配置<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="org.hibernate.tutorial.jpa"> (1)
<description>
Persistence unit for the Jakarta Persistence tutorial of the Hibernate Getting Started Guide
</description>
<class>org.hibernate.tutorial.em.Event</class> (2)
<properties> (3)
<!-- Database connection settings -->
<property name="jakarta.persistence.jdbc.url" value="jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1" />
<property name="jakarta.persistence.jdbc.user" value="sa" />
<property name="jakarta.persistence.jdbc.password" value="" />
<!-- Automatically export the schema -->
<property name="jakarta.persistence.schema-generation.database.action" value="create" />
<!-- Echo all executed SQL to console -->
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.format_sql" value="true" />
<property name="hibernate.highlight_sql" value="true" />
</properties>
</persistence-unit>
</persistence>
1 | persistence.xml 文件应为它声明的每个持久性单元提供唯一名称。应用程序在获取 EntityManagerFactory 时使用此名称来引用配置,稍后我们将看到此名称。 |
2 | <class/> 元素注册我们的注释实体类。 |
3 | 作为 <properties/> 元素指定的设置已在 通过属性文件进行配置 中进行了讨论。在此,尽可能使用 JPA 标准属性名称。 |
前缀带有旧版 Java EE 命名空间 |
3.2. 带注解的实体 Java 类
实体类与带有注释的实体 Java 类中的实体类完全相同。
3.3. 示例代码
以前的教程使用 Hibernate 原生 API。本教程使用标准 Jakarta Persistence API。
protected void setUp() {
entityManagerFactory = Persistence.createEntityManagerFactory("org.hibernate.tutorial.jpa"); (1)
}
1 | 再次注意,持久单元名称为 org.hibernate.tutorial.jpa ,它与我们的persistence.xml中的名称相匹配。 |
持久化和查询实体的代码与持久化实体的代码几乎相同。很不幸的是,EntityManagerFactory
没有像 SessionFactory
一样的好的 inTransaction()
方法,所以我们必须自己编写
void inTransaction(Consumer<EntityManager> work) {
EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
try {
transaction.begin();
work.accept(entityManager);
transaction.commit();
}
catch (Exception e) {
if (transaction.isActive()) {
transaction.rollback();
}
throw e;
}
finally {
entityManager.close();
}
}
如果你在 Java SE 中使用 JPA,则需要将此函数复制/粘贴到你的项目中。或者,你可以将 EntityManagerFactory 展开为 SessionFactory 。 |
3.4. 更进一步!
-
了解如何使用 CDI 在 Quarkus 中注入一个容器管理的
EntityManager
。请参阅 Quarkus Website 以获取说明。
4. 使用 Envers 的教程
-
将一个实体标注为历史记录
-
配置 Envers
-
使用 Envers API 查看和分析历史数据
4.1. persistence.xml
此文件未经之前的更改。
4.2. 带注解的实体 Java 类
此实体类也与我们之前几乎相同。最大的不同之处是添加了注释 @org.hibernate.envers.Audited
,它告知 Envers 自动追踪对此实体的更改。
4.3. 示例代码
此代码保存了某些实体,对其中一个实体进行更改,然后使用 Envers API 提取初始修订以及更新的修订。修订指的是实体的历史记录快照。
org.hibernate.envers.AuditReader
public void testBasicUsage() {
...
AuditReader reader = AuditReaderFactory.get( entityManager ); (1)
Event firstRevision = reader.find( Event.class, 2L, 1 ); (2)
...
Event secondRevision = reader.find( Event.class, 2L, 2 ); (3)
...
}
1 | 从包装了 JPA EntityManager 的 org.hibernate.envers.AuditReaderFactory 中获取 org.hibernate.envers.AuditReader 。 |
2 | find 方法会检索实体的特定修订。第一次调用会检索 id 为 2 的 Event 的修订号 1。 |
3 | 稍后,第二次调用会请求 id 为 2 的 Event 的修订号 2。 |
4.4. 更进一步!
-
提供一个自定义修订实体来额外捕获是谁做出了更改。
-
编写一个查询来仅检索满足某些标准的历史数据。参阅用户指南以查看 Envers 查询如何构建。
-
尝试审计具有各种形式的关系(多对一、多对多等)的实体。尝试检索此类实体的历史版本(修订)并导航对象树。