Hibernate Reactive 中文操作指南

1. Introduction to Hibernate Reactive

使用 Hibernate Reactive 创建一个新项目一点也不难。在本文档中,我们将介绍其中涉及的所有基本工作:

Creating a new project with Hibernate Reactive isn’t hard at all. In this short guide, we’ll cover all the basic work involved in:

  1. setting up and configuring a project, and then

  2. writing Java code to define a data model and access the database.

最后,我们将讨论一些与性能相关的话题,当您使用 Hibernate 开发任何大型项目时,您需要了解这些话题。

Last, we’ll discuss some topics related to performance that you’ll need to understand when you develop any large-scale project using Hibernate.

但是,在开始之前,我们建议您快速浏览 session-example 目录中非常简单的示例程序,其中展示了启动并运行您自己的程序所需的所有“部分”。

But, before you start, we recommend taking a quick look at the very simplistic example program in the session-example directory, which shows off all the "bits" you’ll need to get your own program up and running.

1.1. Information about Hibernate ORM

本文档假设对 Hibernate ORM 或 JPA 的另一种实现有一些了解。如果您以前从未使用过 JPA,这没关系,但您可能需要在本文中的某些要点中参考以下信息来源:

This document assumes some passing familiarity with Hibernate ORM or another implementation of JPA. If you’ve never used JPA before, that’s OK, but you might need to refer to the following sources of information at some points in this text:

  1. the documentation for Hibernate ORM,

  2. the JPA 2.2 specification, or

  3. Java Persistence with Hibernate, the latest edition of the book originally titled Hibernate in Action.

1.2. Setting up a reactive Hibernate project

如果您在 Quarkus 环境之外使用 Hibernate Reactive,您需要:

If you’re using Hibernate Reactive outside of the Quarkus environment, you’ll need to:

  1. include Hibernate Reactive itself, along with the appropriate Vert.x reactive database client, as dependencies of your project, and

  2. configure Hibernate Reactive with information about your database, using Hibernate configuration properties.

或者,如果您想在 Quarkus 中使用 Hibernate Reactive,您可以生成预配置的框架项目 right here

Or, if you want to use Hibernate Reactive in Quarkus, you can generate a preconfigured skeleton project right here.

1.2.1. Including Hibernate Reactive in your project build

向您的项目添加以下依赖项:

Add the following dependency to your project:

其中 {version} 是您使用的 Hibernate Reactive 的版本。

Where {version} is the version of Hibernate Reactive you’re using.

您还需要为数据库添加 Vert.x 反应式数据库驱动程序的依赖项,以下选项之一:

You’ll also need to add a dependency for the Vert.x reactive database driver for your database, one of the following options:

Database

Driver dependency

PostgreSQL or CockroachDB

io.vertx:vertx-pg-client:{vertxSqlClientVersion}

MySQL or MariaDB

io.vertx:vertx-mysql-client:{vertxSqlClientVersion}

DB2

io.vertx:vertx-db2-client:{vertxSqlClientVersion}

SQL Server

io.vertx:vertx-mssql-client:${vertxSqlClientVersion}

Oracle

io.vertx:vertx-oracle-client:${vertxSqlClientVersion}

其中 {vertxSqlClientVersion} 是与您正在使用的 Hibernate Reactive 版本兼容的 Vert.x 版本。

Where {vertxSqlClientVersion} is the version of Vert.x compatible with the version of Hibernate Reactive you’re using.

您不需要依赖于数据库的 JDBC 驱动程序。

You don’t need to depend on the JDBC driver for your database.

1.2.2. Optional dependencies

您还可以选择添加以下任何附加功能:

Optionally, you might also add any of the following additional features:

Optional feature

Dependencies

An SLF4J logging implementation

org.apache.logging.log4j:log4j-core or org.slf4j:slf4j-jdk14

The Hibernate metamodel generator, if you’re using the JPA criteria query API

org.hibernate.orm:hibernate-jpamodelgen

Hibernate Validator

org.hibernate.validator:hibernate-validator and org.glassfish:jakarta.el

Compile-time checking for your HQL queries

org.hibernate:query-validator

Second-level cache support via JCache and EHCache

org.hibernate.orm:hibernate-jcache along with org.ehcache:ehcache

SCRAM authentication support for PostgreSQL

com.ongres.scram:client:2.1

如果您想使用字段级别的延迟提取,还可以将 Hibernate bytecode enhancer 添加到您的 Gradle 构建中。

You might also add the Hibernate bytecode enhancer to your Gradle build if you want to use field-level lazy fetching.

示例程序中包含一个示例 Gradle build

There’s an example Gradle build included in the example program.

1.2.3. Basic configuration

Hibernate Reactive 是通过标准 JPA persistence.xml 文档配置的,该文档必须像往常一样放在 /META-INF 目录中。

Hibernate Reactive is configured via the standard JPA persistence.xml document which must be placed, as usual, in the /META-INF directory.

一个示例 persistence.xml 文件包含在示例程序中。

An example persistence.xml file is included in the example program.

真正特定于 Hibernate Reactive 的唯一必需配置是持久性 <provider> 元素,它必须明确:

The only required configuration that’s really specific to Hibernate Reactive is the persistence <provider> element, which must be explicit:

<provider>org.hibernate.reactive.provider.ReactivePersistenceProvider</provider>

否则,配置几乎完全透明 — 您几乎可以完全按照通常配置 Hibernate ORM 核心那样配置 Hibernate Reactive。

Otherwise, configuration is almost completely transparent—you can configure Hibernate Reactive pretty much exactly as you would usually configure Hibernate ORM core.

就像在常规 JPA 中一样,您应该在 persistence.xml 中列出您的实体类:

Just like in regular JPA, you should list your entity classes in persistence.xml:

<class>org.hibernate.reactive.example.session.Author</class>
<class>org.hibernate.reactive.example.session.Book</class>

documentation for Hibernate ORM 中可能找到 Hibernate 识别的配置属性的完整列表。您永远不需要触及其中的大多数。您在此阶段需要的属性是这三个:

A full list of configuration properties recognized by Hibernate may be found in the documentation for Hibernate ORM. You’ll never need to touch most of these. The properties you do need at this stage are these three:

Configuration property name

Purpose

jakarta.persistence.jdbc.url

JDBC URL of your database

jakarta.persistence.jdbc.user and jakarta.persistence.jdbc.password

Your database credentials

这些配置属性的名称中都有 jdbc,但当然 Hibernate Reactive 中没有 JDBC,这些只是 JPA 规范定义的遗留属性名称。特别是 Hibernate Reactive 本身解析并解释 JDBC URL。

These configuration properties have jdbc in their names, but of course there’s no JDBC in Hibernate Reactive, and these are simply the legacy property names defined by the JPA specification. In particular, Hibernate Reactive itself parses and interprets the JDBC URL.

Vert.x 数据库客户端已内置连接池和准备好的语句缓存。您可能希望控制连接池的大小:

The Vert.x database client has built-in connection pooling and prepared statement caching. You might want to control the size of the connection pool:

Configuration property name

Purpose

hibernate.connection.pool_size

The maximum size of the reactive connection pool

我们将在以后的 @“78”中了解更高级的连接池调优。

We’ll learn about more advanced connection pool tuning later, in Tuning the Vert.x pool.

1.2.4. Automatic schema export

您可以让 Hibernate Reactive 从您在 Java 代码中指定的映射注释中推断出您的数据库架构,并通过指定以下一个或多个配置属性在初始化时导出架构:

You can have Hibernate Reactive infer your database schema from the mapping annotation you’ve specified in your Java code, and export the schema at initialization time by specifying one or more of the following configuration properties:

Configuration property name

Purpose

jakarta.persistence.schema-generation.database.action

If create, first drop the schema and then export tables, sequences, and constraints.If create-only, export tables, sequences, and constraints.If create-drop, drop the schema and recreate it on SessionFactory startup. Additionally, drop the schema on SessionFactory shutdown.If drop, drop the schema on SessionFactory shutdown.If validate, validate the database schema without changing it.If update, only export what’s missing in the schema.

jakarta.persistence.create-database-schemas

(Optional) If true, automatically create schemas and catalogs

jakarta.persistence.schema-generation.create-source

(Optional) If metadata-then-script or script-then-metadata, execute an additional SQL script when exported tables and sequences

jakarta.persistence.schema-generation.create-script-source

(Optional) The name of the SQL script to be executed

此特性对于测试极有帮助。

This feature is extremely useful for testing.

模式导出使用阻塞操作,因此使用时启动工厂可能需要特殊处理。如果不这样做,将会引发异常:

Schema export uses blocking operations so starting the factory might require special handling when using it. Failing to do so will cause an exception:

io.vertx.core.VertxException: Thread blocked

以下方法可解决该问题:

You can solve this issue using executeBlocking:

Vertx vertx = ...

Uni<Void> startHibernate = Uni.createFrom().deferred(() -> {
  emf = Persistence
    .createEntityManagerFactory("demo")
    .unwrap(Mutiny.SessionFactory.class);

  return Uni.createFrom().voidItem();
});

startHibernate = vertx.executeBlocking(startHibernate)
  .onItem().invoke(() -> logger.info("✅ Hibernate Reactive is ready"));

1.2.5. Logging the generated SQL

若要查看发送到数据库的已生成 SQL,请执行以下操作:

To see the generated SQL as it’s sent to the database, either:

  1. set the property hibernate.show_sql to true, or

  2. enable debug-level logging for the category org.hibernate.SQL using your preferred SLF4J logging implementation.

例如,如果您使用的是 Log4j 2(如上面 link:#@“79”文件中的):

For example, if you’re using Log4J 2 (as above in Optional dependencies), add these lines to your _log4j2.properties file:

logger.hibernate.name = org.hibernate.SQL
logger.hibernate.level = debug

一个示例 log4j2.properties 文件包含在示例程序中。

An example log4j2.properties file is included in the example program.

通过启用以下一个或两个设置,你可以使已记录的 SQL 更具可读性:

You can make the logged SQL more readable by enabling one or both of the following settings:

Configuration property name

Purpose

hibernate.format_sql

If true, log SQL in a multiline, indented format

hibernate.highlight_sql

If true, log SQL with syntax highlighting via ANSI escape codes

1.2.6. Minimizing repetitive mapping information

以下属性对于最小化您需要在 @“80”和 @“81”注释中显式指定的信息量非常有用,我们将在下面的 @“82”中讨论这些注释:

The following properties are very useful for minimizing the amount of information you’ll need to explicitly specify in @Table and @Column annotations which we’ll discuss below in Mapping entity classes:

Configuration property name

Purpose

hibernate.default_schema

A default schema name for entities which do not explicitly declare one

hibernate.default_catalog

A default catalog name for entities which do not explicitly declare one

hibernate.physical_naming_strategy

A PhysicalNamingStrategy implementing your database naming standards

1.2.7. Nationalized character data in SQL Server

By default, SQL Server 的 charvarchar 类型不支持 Unicode 数据。因此,如果你使用 SQL Server,你可能需要强制 Hibernate 使用 ncharnvarchar 类型。

By default, SQL Server’s char and varchar types don’t accommodate Unicode data. So, if you’re working with SQL Server, you might need to force Hibernate to use the nchar and nvarchar types.

Configuration property name

Purpose

hibernate.use_nationalized_character_data

Use nchar and nvarchar instead of char and varchar

1.3. Writing the Java code

至此,我们已准备好编写 Java 代码!

With that out of the way, we’re all set to write some Java code!

与使用 Hibernate 的任何项目类似,你的持久化相关代码包含两个主要部分:

As is the case in any project that uses Hibernate, your persistence-related code comes in two main pieces:

  • a representation of your data model in Java, which takes the form of a set of annotated entity classes, and

  • a larger number of functions which interact with Hibernate’s APIs to perform the persistence operations associated with your various transactions.

第一部分,即数据或“域”模型,通常更容易编写,但是出色地、干净利落地完成这项工作会极大地影响您在第二部分的成功。

The first part, the data or "domain" model, is usually easier to write, but doing a great and very clean job of it will strongly affect your success in the second part.

代码的第二部分更难正确编写。此代码必须:

The second part of the code is much trickier to get right. This code must:

  1. manage transactions and reactive sessions,

  2. construct reactive streams by chaining persistence operations invoked on the reactive session,

  3. fetch and prepare data needed by the UI, and

  4. handle failures.

1.3.1. Mapping entity classes

我们在此不会过多介绍实体类,仅仅是因为在 Hibernate Reactive 中映射实体类的原理以及你将使用的实际映射注解与常规 Hibernate ORM 和其他 JPA 实现完全相同。

We won’t have much to say about the entity classes here, simply because the principles behind mapping entity classes in Hibernate Reactive, along with the actual mapping annotations you’ll use, are all identical to regular Hibernate ORM and other implementations of JPA.

例如:

For example:

@Entity
@Table(name="authors")
class Author {
    @Id @GeneratedValue
    private Integer id;

    @NotNull @Size(max=100)
    private String name;

    @OneToMany(mappedBy = "author", cascade = PERSIST)
    private List<Book> books = new ArrayList<>();

    Author(String name) {
        this.name = name;
    }

    Author() {}

    // getters and setters...
}

你可以自由组合和匹配:

You’re quite free to mix and match:

  1. the regular JPA mapping annotations defined in the package `jakarta.persistence. with

  2. the advanced mapping annotations in org.hibernate.annotations, and even

  3. annotations like @NotNull and @Size defined by Bean Validation.

documentation for Hibernate ORM 中可能找到对象/关系映射注释的完整列表。大多数映射注释在 Hibernate Reactive 中已经得到支持,不过此时仍有一些限制。

A full list of object/relational mapping annotations may be found in the documentation for Hibernate ORM. Most mapping annotations are already supported in Hibernate Reactive, though there are still a handful of limitations at this time.

Common JPA annotations

最常用且最有用的映射注解包括这些标准 JPA 注解:

The most common and useful mapping annotations include these standard JPA annotations:

Annotation

Purpose

@Entity

Declares an entity class (a class with its own database table an persistent identity)

@MappedSuperclass

A superclass that declares common persistent fields of its @Entity subclasses

@Embeddable or @Embedded

Declare an embeddable class (a class without its own persistent identity or database table)

@Inheritance

Defines how inheritance hierarchies should be mapped to database tables

@Id

Specifies that a field of an entity holds the persistent identity of the entity, and maps to the primary key of its table

@IdClass

Specifies a class representing the composite primary key of the entity (for entities with multiple @Id fields)

@EmbeddedId

Specifies that a field of an entity holds its composite primary key represented as an @Embeddable class

@GeneratedValue

Specifies that an identifier is a system-generated surrogate key

@Version

Specifies that a field of an entity holds a version number used for optimistic locking

@Enumerated

Maps a field holding an enum

@ManyToOne

Declares a many-to-one association to a second entity

@OneToOne

Declares a one-to-one association to a second entity

@OneToMany

Declares a one-to-many association to a second entity

@Table

Specifies a mapping to a database table

@SecondaryTable

Specifies a mapping to a second database table

@Column

Specifies a mapping to a database column

@JoinColumn

Specifies a mapping to a database foreign key

Useful Hibernate annotations

了解这些 Hibernate 注解也非常有用:

These Hibernate annotations are also quite useful to know about:

Annotation

Purpose

@Cache

Enables second-level caching for an entity

@Formula

Maps field to SQL expression instead of a column

@CreationTimestamp, @UpdateTimestamp

Automatically assign a timestamp to a field

@OptimisticLocking

Enables optimistic locking for entities with no @Version field

@FilterDef and @Filter

Define a Hibernate filter

@FetchProfile

Defines a Hibernate fetch profile

@Generated

Defines a property generated by the database

@ColumnDefault

Specifies a SQL expression used to assign a default value to a column (use in combination with @Generated(INSERT))

@GenericGenerator

Selects a custom id generator

@DynamicInsert and @DynamicUpdate

Generate SQL dynamically with only needed columns (instead of using static SQL generated at startup)

@Fetch

Specifies the fetching mode for an association

@BatchSize

Specifies the batch size for batch fetching an association

@Loader

Specifies a named query used to fetch an entity by id (for example, when find(type, id) is called) in place of the default SQL generated by Hibernate

@SqlInsert, @SqlUpdate, @SqlDelete

Specify custom DML for entity operations

@NaturalId

Marks a field or fields as an alternative "natural" identifier (unique key) of the entity

@Nationalized

Use nchar, nvarchar, or nclob selectively for one particular column.

@Immutable

Specifies that an entity or collection is immutable

@SortNatural or @SortComparator

Maps a SortedSet or SortedMap

@Check

Declares a SQL check constraint to be added to DDL

Bean Validation annotations

有关 Bean Validation 注释的信息可以在 documentation for Hibernate Validator 中找到。

Information about Bean Validation annotations may be found in the documentation for Hibernate Validator.

1.3.2. Getters and setters

在 Hibernate Reactive outside Quarkus 环境中使用时,你需要根据通常的 JPA 约定编写实体类,其中要求:

When using Hibernate Reactive outside the Quarkus environment, you’ll need to write your entity classes according to the usual JPA conventions, which require:

  1. private fields for persistent attributes, and

  2. a nullary constructor.

禁止从实体类外部访问持久字段。因此,必须通过实体类定义的 getter 和 setter 方法来中介对持久字段的外部访问。

It’s illegal to access persistent fields from outside the entity class. Therefore, external access to persistent fields must be intermediated via getter and setter methods defined by the entity class.

在 Quarkus 中使用 Hibernate Reactive 时,这些要求已放松,如果您愿意,可以使用公共字段代替 getter 和 setter。

When you use Hibernate Reactive in Quarkus, these requirements are relaxed, and you can use public fields instead of getters and setters if you prefer.

1.3.3. equals() and hashCode()

实体类应覆盖 equals()hashCode()。对于初次接触 Hibernate 或 JPA 的人来说,经常会对应该在 hashCode() 中包含哪些字段感到困惑,请记住以下原则:

Entity classes should override equals() and hashCode(). People new to Hibernate or JPA are often confused by exactly which fields should be included in the hashCode(), so please keep the following principles in mind:

  1. You should not include mutable fields in the hashcode, since that would require rehashing any collection containing the entity whenever the field is mutated.

  2. It’s not completely wrong to include a generated identifier (surrogate key) in the hashcode, but since the identifier is not generated until the entity instance is made persistent, you must take great care to not add it to any hashed collection before the identifier is generated. We therefore advise against including any database-generated field in the hashcode.

将任何不可变的非生成字段包含在哈希中是没有问题的。

It’s OK to include any immutable, non-generated field in the hashcode.

也就是说,基于实体的生成标识符实现 equals()hashCode() 也可用作 if you’re careful

That said, an implementation of equals() and hashCode() based on the generated identifier of the entity can work if you’re careful.

请注意,即使您已标识出自然键,我们仍建议在外部键中使用生成的代理键,因为这可以使您的数据模型 much 更易于更改。

Note that even when you’ve identified a natural key, we still recommend the use of a generated surrogate key in foreign keys, since this makes your data model much easier to change.

1.3.4. Identifier generation

Hibernate Reactive 的功能与纯 Hibernate 有所不同的一个领域是 ID 生成领域。为与 Hibernate ORM 和 JDBC 配合使用而编写的自定义标识符生成器无法在反应式环境中使用。

One area where the functionality of Hibernate Reactive diverges from plain Hibernate is in the area of id generation. Custom identifier generators written to work with Hibernate ORM and JDBC will not work in the reactive environment.

  1. Sequence, table, and UUID id generation is built in, and these id generation strategies may be selected using the usual JPA mapping annotations: @GeneratedValue, @TableGenerator, @SequenceGenerator.

  2. On MySQL, an autoincrement column may be used by specifying @GeneratedValue(strategy=GenerationType.IDENTITY)

  3. Custom id generators may be defined by implementing ReactiveIdentifierGenerator and declaring the custom implementation using @GenericGenerator.

  4. Natural ids—including composite ids—may be assigned by the program in the usual way.

JPA 规范定义的标准 ID 生成策略可以使用以下注解进行定制:

The standard id generation strategies defined by the JPA specification may be customized using the following annotations:

Annotation

Purpose

@SequenceGenerator

Configure a generator based on a database sequence

@TableGenerator

Configure a generator based on a row of a database table

例如,序列 ID 生成可以这样指定:

For example, sequence id generation may be specified like this:

@Entity
@Table(name="authors")
class Author {
    @Id @GeneratedValue(generator = "authorIds")
    @SequenceGenerator(name = "authorIds",
               sequenceName = "author_ids",
             allocationSize = 20)
    Integer id;
    ...
}

您可以在 JPA 规范中找到更多信息。

You can find more information in the JPA specification.

如果您有非常特殊的要求,那么您可以查看 ReactiveIdentifierGenerator 的 Javadoc,以了解如何实现您自己的自定义 reactive 标志符生成器。

If you have very particular requirements, you can check out the Javadoc of ReactiveIdentifierGenerator for information on how to implement your own custom reactive identifier generator.

1.3.5. Custom types

基于 UserType 接口的 Hibernate 自定义类型针对 JDBC 使用,并依赖于 JDBC 定义的接口。因此,Hibernate Reactive 提供了一个适配器,向 UserType 实现公开了 JDBC 部分实现。

Hibernate custom types based on the UserType interface are targeted toward use with JDBC, and depend on interfaces defined by JDBC. So Hibernate Reactive features an adaptor that exposes a partial implementation of JDBC to the UserType implementation.

因此,some 现有的 UserType 实现可以在 Hibernate Reactive 中使用,具体取决于它们依赖于 JDBC 的哪些特性。

Therefore, some existing UserType implementations will work in Hibernate Reactive, depending upon precisely which features of JDBC they depend on.

您可以通过使用 Hibernate @Type 注解来注释实体类的字段,以指定自定义类型。

You may specify a custom type by annotating a field of an entity class with the Hibernate @Type annotation.

1.3.6. Attribute converters

任何 JPA AttributeConverter 都可以在 Hibernate Reactive 中使用。例如:

Any JPA AttributeConverter works in Hibernate Reactive. For example:

@Converter
public class BigIntegerAsString implements AttributeConverter<BigInteger, String> {
    @Override
    public String convertToDatabaseColumn(BigInteger attribute) {
        return attribute == null ? null : attribute.toString(2);
    }

    @Override
    public BigInteger convertToEntityAttribute(String string) {
        return string == null ? null : new BigInteger(string, 2);
    }
}

您需要使用以下注解中的一种或两种:

You’ll need to use one or both of these annotations:

Annotation

Purpose

@Converter

Declares a class implementing AttributeConverter

@Convert

Specifies an AttributeConverter converter to use for a field of an entity class

您可以在这些注解的 Javadoc 和 JPA 规范中找到更多信息。

You’ll find more information in the Javadoc for these annotations and in the JPA specification.

1.3.7. APIs for chaining reactive operations

当您使用 Hibernate Reactive 编写持久性逻辑时,您大部分时间都在使用 reactive Session。只是为了让新用户有点无所适从,reactive Session 及其相关的接口都有两个版本:

When you write persistence logic using Hibernate Reactive, you’ll be working with a reactive Session most of the time. Just to make things a little more confusing for new users, the reactive Session and its related interfaces all come in two flavors:

  1. Stage.Session and friends provide a reactive API based around Java’s CompletionStage, and

  2. Mutiny.Session and friends provide an API based on Mutiny.

您需要决定要使用哪个 API!

You’ll need to decide which API you want to use!

以下是在使用 Hibernate Reactive 时始终需要的反应流中最重要的操作:

These are the most important operations on reactive streams that you’ll need all the time when working with Hibernate Reactive:

Purpose

Java CompletionStage

Mutiny Uni

Chain non-blocking operations

thenCompose()

chain()

Transform streamed items

thenApply()

map() and replaceWith()

Perform an action using streamed items

thenAccept()

invoke() and call()

Perform cleanup (similar to finally)

whenComplete()

eventually()

在本简介中,我们的代码示例通常使用 Mutiny。如果您更熟悉 CompletionStage,可以参考上表帮助理解代码。

In this introduction, our code examples usually use Mutiny. If you’re more familiar with CompletionStage, you can refer to the above table to help you understand the code.

当我们在本文档中使用术语 reactive stream 时,我们的意思是:

When we use the term reactive stream in this document, we mean:

  1. a chain of _CompletionStage_s, or

  2. a chain of Mutiny _Uni_s and _Multi_s

由程序构建,以服务于特定请求、事务或工作单元。

that is built by the program in order to service a particular request, transaction, or unit of work.

1.3.8. Obtaining a reactive session factory

无论您决定什么,获得 reactive 会话的第一步是获取 JPA EntityManagerFactory,就像您通常在普通 JPA 中获得 JPA EntityManagerFactory 一样,例如,通过调用:

Whatever you decide, the first step to getting a reactive session is to obtain a JPA EntityManagerFactory just as you usually would in plain ol' regular JPA, for example, by calling:

EntityManagerFactory emf = Persistence.createEntityManagerFactory("example");

现在,unwrap() reactive SessionFactory。如果您想使用 CompletionStage_s for chaining reactive operations, ask for a _Stage.SessionFactory

Now, unwrap() the reactive SessionFactory. If you want to use CompletionStage_s for chaining reactive operations, ask for a _Stage.SessionFactory:

Stage.SessionFactory sessionFactory = emf.unwrap(Stage.SessionFactory.class);

或者,如果您更愿意使用基于 Mutiny 的 API,unwrap() 类型 Mutiny.SessionFactory

Or, if you prefer to use the Mutiny-based API, unwrap() the type Mutiny.SessionFactory:

Mutiny.SessionFactory sessionFactory = emf.unwrap(Mutiny.SessionFactory.class);

reactive 会话可以从结果 reactive SessionFactory 中获得。

Reactive sessions may be obtained from the resulting reactive SessionFactory.

1.3.9. Obtaining a reactive session

持久性操作通过 reactive Session 对象公开。理解此接口的大多数操作都是非阻塞的非常重要,并且针对数据库执行 SQL 从不会同步进行。属于单个工作单元的持久性操作必须在单个 reactive 流中通过组合进行链接。

Persistence operations are exposed via a reactive Session object. It’s very important to understand that most operations of this interface are non-blocking, and execution of SQL against the database is never performed synchronously. Persistence operations that belong to a single unit of work must be chained by composition within a single reactive stream.

此外请记住,Hibernate 会话是一个轻量级对象,应该在单个工作逻辑单元内创建、使用,然后丢弃。

Also remember that a Hibernate session is a lightweight object that should be created, used, and then discarded within a single logical unit of work.

若要从 SessionFactory 中获取响应 Session,请使用 withSession()

To obtain a reactive Session from the SessionFactory, use withSession():

sessionFactory.withSession(
        session -> session.find(Book.class, id)
                .invoke(
                    book -> ... //do something with the book
                )
);

生成的对象在自动与当前的响应流相关联,因此在给定流中嵌套调用 in 给定流中嵌套调用 automatically 自动获得相同的共享会话。

The resulting Session object is automatically associated with the current reactive stream, and so nested calls to withSession() in a given stream automatically obtain the same shared session.

或者,您也可以使用 @“83”,但您必须记住在完成后“关闭”会话。而且您必须非常小心地仅从一个 Vert.x 上下文中访问每个会话。(请参阅 @“85”了解更多信息)。

Alternatively, you may use openSession(), but you must remember to close() the session when you’re done. And you must take great care to only access each session from within exactly one Vert.x context. (See Sessions and Vert.x contexts more on this).

Uni<Session> sessionUni = sessionFactory.openSession();
sessionUni.chain(
        session -> session.find(Book.class, id)
                .invoke(
		    book -> ... //do something with the book
                )
                .eventually(session::close)
);

1.3.10. Using the reactive session

接口具有与 JPA 方法同名的多个方法。您可能已熟悉 JPA 定义的以下会话操作:

The Session interface has methods with the same names as methods of the JPA EntityManager. You might already be familiar with the following session operations defined by JPA:

Method name and parameters

Effect

find(Class,Object)

Obtain a persistent object given its type and its id (primary key)

persist(Object)

Make a transient object persistent and schedule a SQL insert statement for later execution

remove(Object)

Make a persistent object transient and schedule a SQL delete statement for later execution

merge(Object)

Copy the state of a given detached object to a corresponding managed persistent instance and return the persistent object

refresh(Object)

Refresh the persistent state of an object using a new SQL select to retrieve the current state from the database

lock(Object)

Obtain a pessimistic lock on a persistent object

flush()

Detect changes made to persistent objects association with the session and synchronize the database state with the state of the session by executing SQL insert, update, and delete statements

detach(Object)

Disassociate a persistent object from a session without affecting the database

getReference(Class,id) or getReference(Object)

Obtain a reference to a persistent object without actually loading its state from the database

如果您不熟悉这些操作,请不要绝望!它们的语义在 JPA 规范和 API 文档中进行定义,并且在无数篇文章和博客文章中进行了说明。但如果您已有一些 Hibernate 或 JPA 经验,那么您已经驾轻就熟了!

If you’re not familiar with these operations, don’t despair! Their semantics are defined in the JPA specification, and in the API documentation, and are explained in innumerable articles and blog posts. But if you already have some experience with Hibernate or JPA, you’re right at home!

现在,here’s where Hibernate Reactive is different: 在响应 API 中,这些方法中的每一个都会通过 Java CompletionStage(或 Mutiny Uni)以非阻塞方式返回其结果。例如:

Now, here’s where Hibernate Reactive is different: in the reactive API, each of these methods returns its result in a non-blocking fashion via a Java CompletionStage (or Mutiny Uni). For example:

session.find(Book.class, book.id)
       .invoke( book -> System.out.println(book.title + " is a great book!") )

另一方面,没有有意义返回值的方法只需返回 (或 )。

On the other hand, methods with no meaningful return value just return CompletionStage<Void> (or Uni<Void>).

session.find(Book.class, id)
       .call( book -> session.remove(book) )
       .call( () -> session.flush() )

使用响应流时,一个 extremely 常见的错误是忘记将“类似 void”的方法的返回值链接起来。例如,在以下代码中,flush() 操作从未执行,因为 invoke() 未将返回值链接到流的末端。

An extremely common mistake when using reactive streams is to forget to chain the return value of a "void-like" method. For example, in the following code, the flush() operation is never executed, because invoke() doesn’t chain its return value to the tip of the stream.

session.find(Book.class, id)
       .call( book -> session.remove(book) )
       .invoke( () -> session.flush() )   //OOPS, WRONG!!

因此,请记住:

So remember:

  1. You must use thenCompose(), not thenAccept(), when calling "void-like" methods that return CompletionStage.

  2. In Mutiny, you must use call(), not invoke(), when calling "void-like" methods that return Uni.

相同的问题发生在以下代码中,但这次是 永远不会被调用的:

The same problem occurs in the following code, but this time it’s remove() that never gets called:

session.find(Book.class, id)
       .call( book -> {
           session.remove(book);   //OOPS, WRONG!!
           return session.flush();
       } )

如果您已有一些响应式编程经验,那么这里没有新的知识点。但如果您 对响应式编程很感兴趣,只要知道以某种形式您将犯一次此类错误!

If you already have some experience with reactive programming, there’s nothing new to learn here. But if you are new to reactive programming, just be aware that you’re going to make this mistake, in some form, at least once!

1.3.11. Queries

自然,接口是一个工厂,用于 实例,使您能够设置查询参数以及执行查询和 DML 语句:

Naturally, the Session interface is a factory for Query instances which allow you to set query parameters and execute queries and DML statements:

Method name

Effect

createQuery()

Obtain a Query for executing a query or DML statement written in HQL or JPQL

createNativeQuery()

Obtain a Query for executing a query or DML statement written in the native SQL dialect of your database

createNamedQuery()

Obtain a Query for executing a named HQL or SQL query defined by a @NamedQuery annotation

该方法生成一个响应式 ,允许异步执行 HQL/JPQL 查询,始终通过 (或 )返回其结果:

That createQuery() method produces a reactive Query, allowing HQL / JPQL queries to be executed asynchronously, always returning their results via a CompletionStage (or Uni):

session.createQuery("select title from Book order by title desc")
       .getResultList()
       .invoke( list -> list.forEach(System.out::println) )

接口定义了以下重要操作:

The Query interface defines the following important operations:

Method name

Effect

setParameter()

Set an argument of a query parameter

setMaxResults()

Limit the number of results returned by the query

setFirstResult()

Specify a certain number of initial results to be skipped (for result pagination)

getSingleResult()

Execute a query and obtain the single result

getResultList()

Execute a query and obtain the results as a list

executeUpdate()

Execute a DML statement and obtain the number of affected rows

对于 JPA 标准查询,你首先必须使用 SessionFactory.getCriteriaBuilder() 获取 CriteriaBuilder,并使用 Session.createQuery() 执行查询。

For JPA criteria queries, you must first obtain the CriteriaBuilder using SessionFactory.getCriteriaBuilder(), and execute your query using Session.createQuery().

CriteriaQuery<Book> query = factory.getCriteriaBuilder().createQuery(Book.class);
Root<Author> a = query.from(Author.class);
Join<Author,Book> b = a.join(Author_.books);
query.where( a.get(Author_.name).in("Neal Stephenson", "William Gibson") );
query.select(b);
return session.createQuery(query).getResultList().invoke(
        books -> books.forEach( book -> out.println(book.title) )
);

1.3.12. Fetching lazy associations

在 Hibernate ORM 中,当关联首次在会话中访问时,将透明地获取延迟关联。而在 Hibernate 响应式中,延迟关联获取是一个异步进程,通过 (或 )产生结果。

In Hibernate ORM, a lazy association is fetched transparently when the association is first accessed within a session. In Hibernate Reactive, on the other hand, lazy association fetching is an asynchronous process that produces a result via a CompletionStage (or Uni).

因此,延迟获取是一个显式操作,称为 ,的静态方法 和 :

Therefore, lazy fetching is an explicit operation named fetch(), a static method of Stage and Mutiny:

session.find(Author.class, author.id)
       .chain( author -> Mutiny.fetch(author.books) )
       .invoke( books -> ... )

当然,如果您急切地获取关联,这不是必需的。

Of course, this isn’t necessary if you fetch the association eagerly.

有时你可能需要将多个调用链接到 fetch(),例如:

Sometimes you might need to chain multiple calls to fetch(), for example:

Mutiny.fetch( session.getReference(detachedAuthor) )
       .chain( author -> Mutiny.fetch(author.books) )
       .invoke( books -> ... )

1.3.13. Field-level lazy fetching

同样,字段级延迟获取(一项高级功能,仅在与 Hibernate 的可选的编译时字节码增强器结合使用时受支持)也是一项显式操作。

Similarly, field-level lazy fetching—an advanced feature, which is only supported in conjunction with Hibernate’s optional compile-time bytecode enhancer—is also an explicit operation.

为声明延迟字段,我们通常使用 JPA 注解:

To declare a lazy field we usually use the JPA @Basic annotation:

@Basic(fetch=LAZY) String isbn;

一个声明为 的可选的一对一关联也被视为字段级的延迟关联。

An optional one-to-one association declared @OneToOne(fetch=LAZY) is also considered field-level lazy.

只有在通过调用 fetch() 操作的重载版本显式请求时,才获取延迟字段:

A lazy field is only fetched if we explicitly request it by calling an overloaded version of the fetch() operation:

session.find(Book.class, book.id)
       .chain( book -> session.fetch(book, Book_.isbn) )
       .invoke( isbn -> ... )

请注意,要获取的字段由 JPA 元模型 标识。

Note that the field to fetch is identified by a JPA metamodel Attribute.

1.3.14. Transactions

withTransaction() 方法在数据库事务范围内执行工作。

The withTransaction() method performs work within the scope of a database transaction.

session.withTransaction( tx -> session.persist(book) )

会话在事务结束后自动刷新。

The session is automatically flushed at the end of the transaction.

对于给定的 Session 对象,对 withTransaction() 的嵌套调用在相同的共享事务上下文中发生。但是,请注意,事务仅为 resource local 事务,委派给底层 Vert.x 数据库客户端,不会跨越多个数据源,也不会与 JPA 容器管理事务集成。

For a given Session object, nested calls to withTransaction() occur within the same shared transaction context. However, notice that the transaction is a resource local transaction only, delegated to the underlying Vert.x database client, and does not span multiple datasources, nor integrate with JPA container-managed transactions.

为了提供额外的便利,有一种打开会话并在一次调用中启动事务的方法:

For extra convenience, there’s a method that opens a session and starts a transaction in one call:

sessionFactory.withTransaction( (session, tx) -> session.persist(book) )

这可能是大多数时间最方便使用的方法。

This is probably the most convenient thing to use most of the time.

1.4. Integrating with Vert.x

在运行时,与数据库的交互在 Vert.x 线程中进行,通常是事件循环线程中。当您编写创建和销毁 Hibernate Reactive 会话的代码时,了解会话如何与线程和 Vert.x contexts 相关非常重要。

At runtime, interaction with the database happens on a Vert.x thread, typically the event loop thread. When you write code that creates and destroys Hibernate Reactive sessions, it’s important to understand how sessions relate to threads and Vert.x contexts.

1.4.1. Sessions and Vert.x contexts

当你使用 withSession()withTransaction() 创建会话时,它会自动与当前 Vert.x local context 关联,并随本地上下文传播,如上文 Obtaining a reactive session 中所述。而且你只能从拥有此本地上下文的线程中使用会话。如果你搞砸了,并从其他线程中使用它,你可能会看到此错误:

When you create a session using withSession() or withTransaction(), it’s automatically associated with the current Vert.x local context, and propagates with the local context, as mentioned above in Obtaining a reactive session. And you’re only allowed to use the session from the thread that owns this local context. If you screw up, and use it from a different thread, you might see this error:

另一方面,如果使用 openSession(),则必须自己管理会话和上下文之间的关联。现在,这在原则上很简单,但您会惊讶于人们经常出错。

On the other hand, if you use openSession(), you’ll have to manage the association between sessions and contexts yourself. Now, that’s in principle straightforward, but you’d be surprised how often people mess up.

不少用户对这个限制感到惊讶。但我们坚持认为这是完全自然的。从你作为会话用户角度来看,会话的一个原子操作就像 flush()find()_或 _getResultList() 等方法。其中任何一种方法都可能导致 multiple interactions with the database。在这种交互之间,会话根本不是一个定义良好的状态。反应流是一种线程,期望反应式编程应该用一阵耀眼的精灵粉尘消除你的并发问题是不合理的。事情不是这样运作的。

More than a few users are surprised by this restriction. But we insist that it’s completely natural. An atomic operation of the session, from your point of view as a user of the session, is a method like flush(), find(), or getResultList(). Any one of those methods can result in multiple interactions with the database. And between such interactions, the session is simply not in a well-defined state. Reactive streams are a kind of thread, and it’s quite unreasonable to expect that reactive programming should vanish away your concurrency problems in a shimmering puff of pixie dust. That’s not how these things work.

例如,我敢打赌您想编写类似的代码:

For example, I bet you would like to be able to write code like this:

List<CompletionStage> list = ...
for (Entity entity : entities) {
    list.add(session.persist(entity));
}
CompletableFuture.allOf(list).thenCompose(session::flush);

抱歉,但这不被允许。并行反应流可能不会共享会话。每个流必须有自己的会话。

Well, we’re sorry, but that’s just not allowed. Parallel reactive streams may not share a session. Each stream must have its own session.

1.4.2. Executing code in a Vert.x context

如果您需要在 Vert.x 上下文的范围内运行一段代码块,但是当前线程与 Context 无关联,该怎么办?一种解决方案是使用 getOrCreateContext() 获取 Vert.x Context 对象,然后调用 runOnContext() 在该上下文中执行代码。

What if you need to run a block of code within the scope of a Vert.x context, but the current thread isn’t associated with a Context? One solution is to obtain a Vert.x Context object using getOrCreateContext() and then call runOnContext() to execute the code in that context.

Context currentContext = Vertx.currentContext();
currentContext.runOnContext( event -> {
    // Here you will be able to use the session
});

在传递给 runOnContext() 的代码块中,您将能够使用与上下文关联的 Hibernate Reactive 会话。

Within the block of code passed to runOnContext(), you’ll be able to use the Hibernate Reactive session associated with the context.

1.4.3. Vert.x instance service

通过 VertxInstance 服务定义 Hibernate Reactive 如何获取 Vert.x 实例。默认实现只在需要时才创建一次。但是,如果您的程序需要控制 Vert.x 实例的创建方式或获取方式,您可以覆盖默认实现并提供您自己的 VertxInstance。让我们考虑这个示例:

The VertxInstance service defines how Hibernate Reactive obtains an instance of Vert.x. The default implementation just creates one the first time it’s needed. But if your program requires control over how the Vert.x instance is created, or how it’s obtained, you can override the default implementation and provide your own VertxInstance. Let’s consider this example:

public class MyVertx implements VertxInstance {

  private final Vertx vertx;

  public MyVertx() {
    this.vertx = Vertx.vertx();
  }

  @Override
  public Vertx getVertx() {
    return vertx;
  }

}

注册此实现的一种方法是对 Hibernate 进行编程配置,例如:

One way to register this implementation is to configure Hibernate programmatically, for example:

Configuration configuration = new Configuration();
StandardServiceRegistryBuilder builder = new ReactiveServiceRegistryBuilder()
        .addService( VertxInstance.class, new MyVertx() )
        .applySettings( configuration.getProperties() );
StandardServiceRegistry registry = builder.build();
SessionFactory sessionFactory = configuration.buildSessionFactory( registry );

另外,您可以实现 ServiceContributor 接口。

Alternatively, you could implement the ServiceContributor interface.

public class MyServiceContributor implements ServiceContributor {
  @Override
  public void contribute(StandardServiceRegistryBuilder serviceRegistryBuilder) {
    serviceRegistryBuilder.addService( VertxInstance.class, new MyVertxProvider() );
  }
}

要注册 ServiceContributor,请将名为 org.hibernate.service.spi.ServiceContributor 的文本文件添加到 /META-INF/services/

To register this ServiceContributor, add a text file named org.hibernate.service.spi.ServiceContributor to /META-INF/services/.

org.myproject.MyServiceContributor

1.5. Tuning and performance

一旦您有一个使用 Hibernate Reactive 访问数据库的程序启动并运行,必然会发现一些性能令人失望或无法接受的地方。

Once you have a program up and running using Hibernate Reactive to access the database, it’s inevitable that you’ll find places where performance is disappointing or unacceptable.

幸运的是,只要记住一些简单的原则,大多数性能问题都可以通过 Hibernate 为你提供的工具轻松解决。

Fortunately, most performance problems are relatively easy to solve with the tools that Hibernate makes available to you, as long as you keep a couple of simple principles in mind.

首先也是最重要的:您使用 Hibernate Reactive 的原因是因为它让事情变得更简单。如果对于某个问题,它会让事情变得 harder,请停止使用它。相反,使用不同的工具解决此问题。

First and most important: the reason you’re using Hibernate Reactive is because it makes things easier. If, for a certain problem, it’s making things harder, stop using it. Solve this problem with a different tool instead.

第二:在使用 Hibernate 的程序中,有两个主要的潜在性能瓶颈来源:

Second: there are two main potential sources of performance bottlenecks in a program that uses Hibernate:

  1. too many round trips to the database, and

  2. memory consumption associated with the first-level (session) cache.

因此,性能调优主要涉及减少对数据库的访问次数,和/或控制会话缓存的大小。

So performance tuning primarily involves reducing the number of accesses to the database, and/or controlling the size of the session cache.

但在我们讨论这些更高级的话题之前,我们应该先调整连接池。

But before we get to those more advanced topics, we should start by tuning the connection pool.

1.5.1. Tuning the Vert.x pool

在 @“86”中,我们已经看到如何设置 Vert.x 数据库连接池的大小。当需要进行性能调优时,可以通过以下配置属性进一步自定义池和已准备的语句缓存:

In Basic configuration we already saw how to set the size of the Vert.x database connection pool. When it comes time for performance tuning, you can further customize the pool and prepared statement cache via the following configuration properties:

Configuration property name

Purpose

hibernate.vertx.pool.max_wait_queue_size

The maximum connection requests allowed in the wait queue

hibernate.vertx.pool.connect_timeout

The maximum time to wait when requesting a pooled connection, in milliseconds

hibernate.vertx.pool.idle_timeout

The maximum time a connection may sit idle, in milliseconds

hibernate.vertx.pool.cleaner_period

The Vert.x connection pool cleaner period, in milliseconds

hibernate.vertx.prepared_statement_cache.max_size

The maximum size of the prepared statement cache

hibernate.vertx.prepared_statement_cache.sql_limit

The maximum length of prepared statement SQL string that will be cached

最后,对于更高级的案例,您可以编写自己的代码来通过实现 SqlClientPoolConfiguration 配置 Vert.x 客户端。

Finally, for more advanced cases, you can write your own code to configure the Vert.x client by implementing SqlClientPoolConfiguration.

Configuration property name

Purpose

hibernate.vertx.pool.configuration_class

A class implementing SqlClientPoolConfiguration

1.5.2. Enabling statement batching

提高某些事务性能且几乎不需要任何工作的一种简单方法是启用自动 DML 语句批处理。只有在程序在一个事务中对同一张表执行许多插入、更新或删除操作的情况下,批处理才能提供帮助。

An easy way to improve performance of some transactions with almost no work at all is to turn on automatic DML statement batching. Batching only helps in cases where a program executes many inserts, updates, or deletes against the same table in a single transaction.

您需要做的所有事情是设置一个属性:

All you need to do is set a single property:

Configuration property name

Purpose

hibernate.jdbc.batch_size

Maximum batch size for SQL statement batching

(同样,该属性名称中包含 jdbc,但 Hibernate Reactive 重新设置其用于响应连接。)

(Again, this property has jdbc in its name, but Hibernate Reactive repurposes it for use with the reactive connection.)

1.5.3. Association fetching

在 ORM 中实现高性能意味着尽量减少与数据库的往返次数。无论在何时使用 Hibernate 编写数据访问代码时,这个目标都应该放在首位。ORM 中最基本的经验法则就是:

Achieving high performance in ORM means minimizing the number of round trips to the database. This goal should be uppermost in your mind whenever you’re writing data access code with Hibernate. The most fundamental rule of thumb in ORM is:

  1. explicitly specify all the data you’re going to need right at the start of a session/transaction, and fetch it immediately in one or two queries,

  2. and only then start navigating associations between persistent entities.

毫无疑问,Java 程序中执行不佳的数据访问代码最常见的原因是 N+1 selects 的问题。此处,在初始查询中从数据库中检索了一行 N 行,然后使用 N 个后续查询来获取相关实体的关联实例。

Without question, the most common cause of poorly-performing data access code in Java programs is the problem of N+1 selects. Here, a list of N rows is retrieved from the database in an initial query, and then associated instances of a related entity are fetched using N subsequent queries.

Hibernate 提供了多种策略用于高效获取关联并避免 N+1 选择:

Hibernate provides several strategies for efficiently fetching associations and avoiding N+1 selects:

  1. outer join fetching,

  2. batch fetching, and

  3. subselect fetching.

其中,您几乎应始终使用外部连接获取。批获取和子选择仅在极少数情况下有用,其中外部连接获取将导致笛卡尔积和巨大的结果集。不幸的是,外部连接获取根本无法与惰性获取一起使用。

Of these, you should almost always use outer join fetching. Batch fetching and subselect fetching are only useful in rare cases where outer join fetching would result in a cartesian product and a huge result set. Unfortunately, outer join fetching simply isn’t possible with lazy fetching.

由此建议,你不应该太频繁地使用 Stage.fetch()Mutiny.fetch()

It follows from this tip that you shouldn’t need to use Stage.fetch() or Mutiny.fetch() very often!

现在,我们并不是在说关联应该默认映射为急切获取!那将是一个可怕的想法,导致获取整个数据库的简单会话操作!因此:

Now, we’re not saying that associations should be mapped for eager fetching by default! That would be a terrible idea, resulting in simple session operations that fetch the entire database! Therefore:

听起来这个提示与前面的提示相矛盾,但事实并非如此。它表示你必须明确指定关联的快速提取,仅在需要的时候和需要的地方提取。

It sounds as if this tip is in contradiction to the previous one, but it’s not. It’s saying that you must explicitly specify eager fetching for associations precisely when and where they are needed.

如果您在某些特定事务中需要急切获取,请使用:

If you need eager fetching in some particular transaction, use:

  1. left join fetch in HQL,

  2. a fetch profile,

  3. a JPA EntityGraph, or

  4. fetch() in a criteria query.

您可以在 documentation for Hibernate ORM 中找到有关关联获取的更多信息。

You can find much more information about association fetching in the documentation for Hibernate ORM.

1.5.4. Enabling the second-level cache

减少访问数据库次数的经典方法是使用二级缓存,允许在会话之间共享缓存数据。

A classic way to reduce the number of accesses to the database is to use a second-level cache, allowing cached data to be shared between sessions.

Hibernate Reactive 支持执行无阻塞 I/O 的二级缓存实现。

Hibernate Reactive supports second-level cache implementations that perform no blocking I/O.

配置 Hibernate 的二级缓存是一个相当复杂的话题,并且超出了本文的范围。但如果它有帮助,我们正在使用以下配置测试 Hibernate Reactive,它使用 EHCache 作为上文 Optional dependencies 中的缓存实现:

Configuring Hibernate’s second-level cache is a rather involved topic, and quite outside the scope of this document. But in case it helps, we’re testing Hibernate Reactive with the following configuration, which uses EHCache as the cache implementation, as above in Optional dependencies:

Configuration property name

Property value

hibernate.cache.use_second_level_cache

true

hibernate.cache.region.factory_class

org.hibernate.cache.jcache.JCacheRegionFactory

hibernate.javax.cache.provider

org.ehcache.jsr107.EhcacheCachingProvider

hibernate.javax.cache.uri

/ehcache.xml

如果您正在使用 EHCache,您还需要包含一个 ehcache.xml 文件,该文件明确配置属于您的实体和集合的每个缓存区域的行为。

If you’re using EHCache, you’ll also need to include an ehcache.xml file that explicitly configures the behavior of each cache region belonging to your entities and collections.

你可以在 documentation for Hibernate ORM 中找到有关二级缓存的更多信息。

You can find much more information about the second-level cache in the documentation for Hibernate ORM.

1.5.5. Session cache management

不再需要实体实例时,它们不会自动从会话缓存中逐出。(在这方面,会话缓存与二级缓存完全不同!)相反,它们会一直保留在内存中,直到它们所属的会话被您的程序丢弃。

Entity instances aren’t automatically evicted from the session cache when they’re no longer needed. (The session cache is quite different to the second-level cache in this respect!) Instead, they stay pinned in memory until the session they belong to is discarded by your program.

方法 detach()clear() 允许你从会话缓存中删除实体,使其可供垃圾回收。由于大多数会话的持续时间都很短,因此你不太需要进行这些操作。而且如果你发现自己在想自己在某种情况下需要 do,则应该认真考虑替代解决方案:一个 stateless session

The methods detach() and clear() allow you to remove entities from the session cache, making them available for garbage collection. Since most sessions are rather short-lived, you won’t need these operations very often. And if you find yourself thinking you do need them in a certain situation, you should strongly consider an alternative solution: a stateless session.

1.5.6. Stateless sessions

Hibernate 的一个可能不受重视的功能是 StatelessSession 接口,它提供了一种面向命令、更接近底层的与数据库交互的方法。

An arguably-underappreciated feature of Hibernate is the StatelessSession interface, which provides a command-oriented, more bare-metal approach to interacting with the database.

您可从 SessionFactory 中获取无状态的响应式会话:

You may obtain a reactive stateless session from the SessionFactory:

Stage.StatelessSession ss = getSessionFactory().openStatelessSession();

一个无状态会话:

A stateless session:

  1. doesn’t have a first-level cache (persistence context), nor does it interact with any second-level caches, and

  2. doesn’t implement transactional write-behind or automatic dirty checking, so all operations are executed immediately when they’re explicitly called.

对于无状态会话,您始终使用分离对象。因此,编程模型有点不同:

For a stateless session, you’re always working with detached objects. Thus, the programming model is a bit different:

Method name and parameters

Effect

get(Class, Object)

Obtain a detached object, given its type and its id, by executing a select

fetch(Object)

Fetch an association of a detached object

refresh(Object)

Refresh the state of a detached object by executing a select

insert(Object)

Immediately insert the state of the given transient object into the database

update(Object)

Immediately update the state of the given detached object in the database

delete(Object)

Immediately delete the state of the given detached object from the database

在某些情况下,这使得无状态会话更易于使用,但需要注意的是,无状态会话更容易受到数据别名效应的影响,因为很容易获得两个非标识的 Java 对象,它们都表示数据库表的同一行。

In certain circumstances, this makes stateless sessions easier to work with, but with the caveat that a stateless session is much more vulnerable to data aliasing effects, since it’s easy to get two non-identical Java objects which both represent the same row of a database table.

特别是,缺乏持久性上下文意味着你可以安全地执行批量处理任务,而无需分配大量的内存。使用 StatelessSession 无需调用:

In particular, the absence of a persistence context means that you can safely perform bulk-processing tasks without allocating huge quantities of memory. Use of a StatelessSession alleviates the need to call:

  1. clear() or detach() to perform first-level cache management, and

  2. setCacheMode() to bypass interaction with the second-level cache.

在使用无状态会话时,您应该了解以下额外的限制:

When using a stateless session, you should be aware of the following additional limitations:

  1. persistence operations never cascade to associated instances,

  2. changes to @ManyToMany associations and _@ElementCollection_s cannot be made persistent, and

  3. operations performed via a stateless session bypass callbacks.

1.5.7. Optimistic and pessimistic locking

最后,我们没有在上面提到的受载期间行为的一个方面是行级别数据争用。当许多事务尝试读取和更新相同数据时,程序可能会因锁升级、死锁和锁获取超时错误而失去响应。

Finally, an aspect of behavior under load that we didn’t mention above is row-level data contention. When many transactions try to read and update the same data, the program might become unresponsive with lock escalation, deadlocks, and lock acquisition timeout errors.

Hibernate 中有两种基本的数据并发方法:

There’s two basic approaches to data concurrency in Hibernate:

  1. optimistic locking using @Version columns, and

  2. database-level pessimistic locking using the SQL for update syntax (or equivalent).

在 Hibernate 社区中,使用乐观锁定更常见,而 Hibernate 让这件事变得非常容易。

In the Hibernate community it’s much more common to use optimistic locking, and Hibernate makes that incredibly easy.

也就是说,那里 is 也有悲观锁,它有时可以降低事务回滚的可能性。

That said, there is also a place for pessimistic locks, which can sometimes reduce the probability of transaction rollbacks.

因此,响应式会话的 find()lock()、和 refresh() 方法接受一个可选的 LockMode。您还可以为查询指定 LockMode。锁定模式可用于请求悲观锁定,或自定义乐观锁定的行为:

Therefore, the find(), lock(), and refresh() methods of the reactive session accept an optional LockMode. You can also specify a LockMode for a query. The lock mode can be used to request a pessimistic lock, or to customize the behavior of optimistic locking:

LockMode type

Meaning

READ

An optimistic lock obtained implicitly whenever an entity is read from the database using select

OPTIMISTIC

An optimistic lock obtained when an entity is read from the database, and verified using a select to check the version when the transaction completes

OPTIMISTIC_FORCE_INCREMENT

An optimistic lock obtained when an entity is read from the database, and enforced using an update to increment the version when the transaction completes

WRITE

A pessimistic lock obtained implicitly whenever an entity is written to the database using update or insert

PESSIMISTIC_READ

A pessimistic for share lock

PESSIMISTIC_WRITE

A pessimistic for update lock

PESSIMISTIC_FORCE_INCREMENT

A pessimistic lock enforced using an immediate update to increment the version

1.6. Custom connection management and multitenancy

Hibernate Reactive 支持响应式连接的自定义管理,允许您定义 ReactiveConnectionPool 或者扩展内置实现 DefaultSqlClientPool 的自定义实现。

Hibernate Reactive supports custom management of reactive connections by letting you define your own implementation of ReactiveConnectionPool, or extend the built-in implementation DefaultSqlClientPool.

Configuration property name

Value

hibernate.vertx.pool.class

A class which implements ReactiveConnectionPool

定义自定义池的常见动机是对多租户的支持需求。在多租户应用程序中,数据库或数据库模式依赖于当前租户标识符。在 Hibernate Reactive 中设置它的最简单方法是扩展 DefaultSqlClientPool 并覆盖 getTenantPool(String tenantId)

A common motivation for defining a custom pool is the need to support multitenancy. In a multitenant application, the database or database schema depends on the current tenant identifier. The easiest way to set this up in Hibernate Reactive is to extend DefaultSqlClientPool and override getTenantPool(String tenantId).

对于多租户,可能还需要设置由 Hibernate ORM 定义的以下配置属性:

For multitenancy, might also need to set the following configuration property defined by Hibernate ORM:

Configuration property name

Value

hibernate.tenant_identifier_resolver

(Optional) A class which implements CurrentTenantIdentifierResolver

如果您不提供 CurrentTenantIdentifierResolver,您可以在调用 openSession()withSession() 或者 withTransaction() 时显式地指定租户 id。

If you don’t provide a CurrentTenantIdentifierResolver, you can specify the tenant id explicitly when you call openSession(), withSession(), or withTransaction().

1.7. Next steps

Hibernate Reactive 现在集成在 QuarkusPanache 中。Quarkus 中的配置略有不同,因此请务必查看 Quarkus 文档以了解详细信息。

Hibernate Reactive is now integrated in Quarkus and Panache. Configuration works slightly differently in Quarkus, so be sure to check the Quarkus documentation for details.