A reactive API for Hibernate ORM, supporting non-blocking database drivers and a reactive style of interaction with the database.
Hibernate Reactive may be used in any plain Java program, but is especially targeted toward usage in reactive environments like Quarkus and Vert.x.
Currently PostgreSQL, MySQL, and DB2 are supported.
Hibernate Reactive has been tested with:
- Java 8
- PostgreSQL 12
- MySQL 8
- DB2 11.5
- Hibernate ORM 5.4.17.Final
- Vert.x Reactive PostgreSQL Client 3.9.1
- Vert.x Reactive MySQL Client 3.9.1
- Vert.x Reactive DB2 Client 3.9.1
Support for SQL Server is coming soon.
Integration with Quarkus has been developed and tested but has not yet been released.
Usage is very straightforward for anyone with any prior experience with Hibernate or JPA.
Add the following dependency to your project:
org.hibernate.reactive:hibernate-reactive-core:1.0.0-SNAPSHOT
You'll also need to add a dependency for the Vert.x reactive database driver for your database, for example:
io.vertx:vertx-pg-client
for Postgres,io.vertx:vertx-mysql-client
for MySQL, orio.vertx:vertx-db2-client
for DB2.
There's an example Gradle build included in the example program.
Use the regular JPA mapping annotations defined in the package
javax.persistence
, and/or the Hibernate mapping annotations in
org.hibernate.annotations
.
Most mapping annotations are already supported in Hibernate Reactive. The annotations which are not yet supported are listed in Limitations, below.
Hibernate Reactive is configured via the standard JPA persistence.xml
document which must be placed, as usual, in the /META-INF
directory.
The only configuration specific to Hibernate Reactive is the persistence
<provider>
element, which must be explicit:
<provider>org.hibernate.reactive.provider.ReactivePersistenceProvider</provider>
Otherwise, configuration is almost completely transparent. Configure Hibernate exactly as you usually would, noting that most configuration properties related to JDBC or JTA aren't relevant in the context of Hibernate Reactive.
Configuration properties of particular interest include:
javax.persistence.jdbc.url
, the JDBC URL of your database,javax.persistence.jdbc.user
andjavax.persistence.jdbc.password
, the database credentials, andhibernate.connection.pool_size
, the size of the Vert.x reactive connection pool.
The Vert.x database client has built-in connection pooling and prepared statement caching. During performance tuning, you can further customize the pool and cache via the following properties:
hibernate.vertx.pool.max_wait_queue_size
hibernate.vertx.prepared_statement_cache.max_size
hibernate.vertx.prepared_statement_cache.sql_limit
(But for now, just leave these settings alone.)
An example persistence.xml
file is included in the example
program.
Obtain a Hibernate SessionFactory
or JPA EntityManagerFactory
just as you normally would, for example, by calling:
EntityManagerFactory emf = Persistence.createEntityManagerFactory("example");
Now, unwrap()
the reactive SessionFactory
:
Stage.SessionFactory sessionFactory = emf.unwrap(Stage.SessionFactory.class);
The type Stage.SessionFactory
gives access to reactive APIs based on
Java's CompletionStage
.
If you prefer to use the Mutiny-based API, unwrap()
the type
Mutiny.SessionFactory
:
Mutiny.SessionFactory sessionFactory = emf.unwrap(Mutiny.SessionFactory.class);
To obtain a reactive Session
from the SessionFactory
, use withSession()
:
sessionFactory.withSession(
session -> ... //do some work
);
Alternatively, you can use openSession()
, but you must remember to close()
the session when you're done.
sessionFactory.openSession()
.thenCompose(
session -> ... //do some work
.whenComplete( ($,e) -> session.close() )
);
The Session
interface has methods with the same names as methods of the
JPA EntityManager
. However, each of these methods returns its result via
a CompletionStage
(or Mutiny Uni
), for example:
session1.find(Book.class, book.id)
.thenAccept( book -> System.out.println(book.title + " is a great book!") )
Methods with no meaningful return value return a reference to the Session
:
session2.persist(book)
.thenCompose( $ -> session2.flush() )
.whenComplete( ($,e) -> session2.close() )
That createQuery()
method produces a reactive Query
, allowing HQL / JPQL
queries to be executed asynchronously, always returning their results via a
CompletionStage
:
session3.createQuery("select title from Book order by title desc")
.getResultList()
.thenAccept(System.out::println)
If you already know Hibernate, and if you already have some experience with reactive programming, there's not much new to learn here: you should immediately feel right at home.
In Hibernate ORM, lazy associations are fetched transparently when the
association is fist accessed within a session. In Hibernate Reactive,
association fetching is an asynchronous process that produces a result
via a CompletionStage
(or Mutiny Uni
).
Therefore, lazy fetching is an explicit operation named fetch()
,
a static method of Stage
and Mutiny
:
session4.find(Author.class, author.id)
.thenCompose( author -> Stage.fetch(author.books) )
.thenAccept( books -> ... )
Of course, this isn't necessary if you fetch the association eagerly.
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:
session5.find(Book.class, book.id)
.thenCompose( book -> session.fetch(book, Book_.isbn) )
.thenAccept( isbn -> ... )
Note that the field to fetch is identified by a JPA metamodel Attribute
.
We don't encourage you to use field-level lazy fetching unless you have very specific requirements.
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.
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) )
Sequence, table, and UUID
id generation is built in, along with support
for assigned ids.
Custom id generators may be defined by implementing ReactiveIdentifierGenerator
and declaring the custom implementation using @GenericGenerator
.
There is a very simple example program in the example
directory.
The project is built with Gradle, but you do not need to have Gradle installed on your machine.
To compile this project, navigate to the hibernate-reactive
directory,
and type:
./gradlew compileJava
To publish Hibernate Reactive to your local Maven repository, run:
./gradlew publishToMavenLocal
To run the tests, you'll need to decide which RDBMS you want to test with, and then get an instance of the test database running on your machine.
By default, the tests will be run against PostgreSQL. To test against
MySQL or DB2, you must explicitly specify -Pdb=mysql
or -Pdb=db2
,
for example:
./gradlew test -Pdb=db2
There are three ways to start the test database.
If you have Docker installed, running the tests is really easy. You don't need to create the test databases manually. Just type:
./gradlew test -Pdocker
Or:
./gradlew test -Pdocker -Pdb=mysql
Or:
./gradlew test -Pdocker -Pdb=db2
The tests will run faster if you reuse the same containers across
multiple test runs. To do this, edit the testcontainers configuration
file .testcontainers.properties
in your home directory, adding the
line testcontainers.reuse.enable=true
. (Just create the file if it
doesn't already exist.)
If you already have PostgreSQL installed on your machine, you'll just need to create the test database. From the command line, type the following commands:
psql
create database hreact;
create user hreact with password 'hreact';
grant all privileges on database hreact to hreact;
Then run ./gradlew test
from the hibernate-reactive
directory.
If you have MySQL installed, you can create the test database using the following commands:
mysql -uroot
create database hreact;
create user hreact identified by 'hreact';
grant all on hreact.* to hreact;
Then run ./gradlew test -Pdb=mysql
from the hibernate-reactive
directory.
If you use Podman, you can start the test database by following the instructions in podman.md.
To run the tests, type ./gradlew test
from the hibernate-reactive
directory.
We're working hard to support the full feature set of Hibernate ORM. At present a few limitations remain.
At this time, Hibernate Reactive does not support the following mapping features:
@ElementCollection
s,@ManyToMany
associations, and- one-sided
@OneToMany
associations withoutmappedBy
.
Instead, use @OneToMany(mappedBy=...)
together with @ManyToOne
for
all associations.
There is no block optimization for the SEQUENCE
and TABLE
id generators.
Currently there is no support for batched inserts and updates. The setting
hibernate.jdbc.batch_size
is ignored.
HQL update
and delete
queries which affect multiple tables (due to the
use of TABLE_PER_CLASS
or JOINED
inheritance mapping) are not working.
The query cache is not yet supported.
Note that you should not use Hibernate Reactive with a second-level cache implementation which performs blocking IO, for example passivation to the filesystem or distributed replication.
You might run into some limitations of the Vert.x DB2 client when using Hibernate Reactive with DB2.