This how-to shows how to build a Vert.x service with Hibernate Reactive.
-
You will build a HTTP service that can list, fetch and record products.
-
Database access will be made using the Hibernate Reactive APIs and a Reactive Vert.x SQL Client.
-
To simplify the experience the database will be started as a PostgreSQL container with TestContainers.
-
A text editor or IDE
-
Java 11 or higher
-
Apache Maven
-
Docker
-
HTTPie to make HTTP requests from the command-line
Here is the content of the pom.xml file that you should be using:
pom.xml
link:pom.xml[role=include]
The project uses the Vert.x Mutiny bindings, so you will use Mutiny to compose asynchronous operations.
The service fits in 2 classes:
-
MainVerticle
contains themain
method as well as the sole verticle, and -
Product
is an entity to be managed with Hibernate.
Let’s start with the Product
entity.
A product has a primary key, a name and a price:
link:src/main/java/io/vertx/howtos/hr/Product.java[role=include]
The configuration of Hibernate Reactive is not very different from regular Hibernate.
We need a META-INF/persistence.xml
file as:
link:src/main/resources/META-INF/persistence.xml[role=include]
-
Specify the reactive provider
-
Make sure
Product
will be a managed entity -
This is a JDBC-style URL, but be assured that it won’t use JDBC!
Hibernate Reactive selects the Vert.x reactive PostgreSQL driver because the persistence.xml
file uses a PostgreSQL URL, and because io.smallrye.reactive:smallrye-mutiny-vertx-pg-client
brings the driver implementation on the classpath.
First of all, we start a PostgreSQL container with TestContainers in the main
method, as in:
link:src/main/java/io/vertx/howtos/hr/MainVerticle.java[role=include]
Once the container is available, we can create a Vertx
context and deploy MainVerticle
:
link:src/main/java/io/vertx/howtos/hr/MainVerticle.java[role=include]
-
We need to pass the PostgreSQL port that is mapped outside the container
-
Because we are using Mutiny, we need to subscribe to the deployment operation and actually trigger it, otherwise nothing happens
The verticle in class MainVerticle
uses the Mutiny bindings, so there is a Mutiny-friendly method to define the start behavior.
This method call asyncStart
returns a Uni<Void>
:
link:src/main/java/io/vertx/howtos/hr/MainVerticle.java[role=include]
-
This is the Hibernate Reactive session factory with a Mutiny-based API
The asyncStart
method needs to complete 2 operations:
-
create an Hibernate Reactive session factory connected to the PostgreSQL database, and
-
start a HTTP server.
We can do these operations simultaneously rather than in sequence.
The session factory is created using the following code, where we override the "JDBC" URL with the PostgreSQL port since it it dynamically allocated. Also note that creating session factory is a blocking operation, so we offload it to a work thread.
link:src/main/java/io/vertx/howtos/hr/MainVerticle.java[role=include]
-
Override the PostgreSQL port
-
Offload to a worker thread
The HTTP API is defined using 3 routes: 1 to get all products, 1 to get a specific product, and 1 to create / record a new product:
link:src/main/java/io/vertx/howtos/hr/MainVerticle.java[role=include]
We can finally start the HTTP server then await for Hibernate Reactive to be ready:
link:src/main/java/io/vertx/howtos/hr/MainVerticle.java[role=include]
-
Join the 2 asynchronous operations, then discard the values and return a
Void
value, henceUni<Void>
Once the 2 operations have completed then the asyncStart
method reports that the verticle deployment has completed.
The methods to handle HTTP requests use Hibernate Reactive for database access:
link:src/main/java/io/vertx/howtos/hr/MainVerticle.java[role=include]
These are standard Hibernate operations (e.g., persist
, flush
, find
) chained using Mutiny operators (e.g., chain
, onItem
, replaceWith
).
The application is self-contained as it starts a PostgreSQL container. You can use Maven to compile and run the application:
$ mvn compile exec:java
The logs should be similar to:
[INFO] --- exec-maven-plugin:3.0.0:java (default-cli) @ hibernate-reactive-howto ---
2021-05-18 13:54:35,570 INFO [io.vertx.howtos.hr.MainVerticle.main()] io.vertx.howtos.hr.MainVerticle - 🚀 Starting a PostgreSQL container
2021-05-18 13:54:39,430 DEBUG [io.vertx.howtos.hr.MainVerticle.main()] 🐳 [postgres:11-alpine] - Starting container: postgres:11-alpine
2021-05-18 13:54:39,431 DEBUG [io.vertx.howtos.hr.MainVerticle.main()] 🐳 [postgres:11-alpine] - Trying to start container: postgres:11-alpine (attempt 1/1)
2021-05-18 13:54:39,432 DEBUG [io.vertx.howtos.hr.MainVerticle.main()] 🐳 [postgres:11-alpine] - Starting container: postgres:11-alpine
2021-05-18 13:54:39,432 INFO [io.vertx.howtos.hr.MainVerticle.main()] 🐳 [postgres:11-alpine] - Creating container for image: postgres:11-alpine
2021-05-18 13:54:40,016 INFO [io.vertx.howtos.hr.MainVerticle.main()] 🐳 [postgres:11-alpine] - Starting container with ID: f538f59149d72ebec87382b09624240ca2faddcbc9c247a53575a537d1d7f045
2021-05-18 13:54:42,050 INFO [io.vertx.howtos.hr.MainVerticle.main()] 🐳 [postgres:11-alpine] - Container postgres:11-alpine is starting: f538f59149d72ebec87382b09624240ca2faddcbc9c247a53575a537d1d7f045
2021-05-18 13:54:43,918 INFO [io.vertx.howtos.hr.MainVerticle.main()] 🐳 [postgres:11-alpine] - Container postgres:11-alpine started in PT4.510869S
2021-05-18 13:54:43,918 INFO [io.vertx.howtos.hr.MainVerticle.main()] io.vertx.howtos.hr.MainVerticle - 🚀 Starting Vert.x
2021-05-18 13:54:44,342 INFO [vert.x-worker-thread-0] org.hibernate.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: pg-demo]
2021-05-18 13:54:44,408 INFO [vert.x-worker-thread-0] org.hibernate.Version - HHH000412: Hibernate ORM core version 5.4.31.Final
2021-05-18 13:54:44,581 INFO [vert.x-eventloop-thread-0] io.vertx.howtos.hr.MainVerticle - ✅ HTTP server listening on port 8080
2021-05-18 13:54:44,586 INFO [vert.x-worker-thread-0] org.hibernate.annotations.common.Version - HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2021-05-18 13:54:44,775 INFO [vert.x-worker-thread-0] org.hibernate.dialect.Dialect - HHH000400: Using dialect: org.hibernate.dialect.PostgreSQL10Dialect
2021-05-18 13:54:45,006 INFO [vert.x-worker-thread-0] o.h.reactive.provider.impl.ReactiveIntegrator - HRX000001: Hibernate Reactive Preview
2021-05-18 13:54:45,338 INFO [vert.x-worker-thread-0] o.h.reactive.pool.impl.DefaultSqlClientPool - HRX000011: SQL Client URL [jdbc:postgresql://localhost:55019/postgres]
2021-05-18 13:54:45,342 WARN [vert.x-worker-thread-0] io.vertx.core.impl.VertxImpl - You're already on a Vert.x context, are you sure you want to create a new Vertx instance?
2021-05-18 13:54:45,345 INFO [vert.x-worker-thread-0] o.h.reactive.pool.impl.DefaultSqlClientPool - HRX000012: Connection pool size: 10
[Hibernate]
drop table if exists Product cascade
2021-05-18 13:54:45,521 WARN [vert.x-eventloop-thread-0] io.vertx.sqlclient.impl.SocketConnectionBase - Backend notice: severity='NOTICE', code='00000', message='table "product" does not exist, skipping', detail='null', hint='null', position='null', internalPosition='null', internalQuery='null', where='null', file='tablecmds.c', line='1060', routine='DropErrorMsgNonExistent', schema='null', table='null', column='null', dataType='null', constraint='null'
[Hibernate]
drop sequence if exists hibernate_sequence
2021-05-18 13:54:45,527 WARN [vert.x-eventloop-thread-0] io.vertx.sqlclient.impl.SocketConnectionBase - Backend notice: severity='NOTICE', code='00000', message='sequence "hibernate_sequence" does not exist, skipping', detail='null', hint='null', position='null', internalPosition='null', internalQuery='null', where='null', file='tablecmds.c', line='1060', routine='DropErrorMsgNonExistent', schema='null', table='null', column='null', dataType='null', constraint='null'
[Hibernate]
drop table if exists Product cascade
2021-05-18 13:54:45,537 WARN [vert.x-eventloop-thread-0] io.vertx.sqlclient.impl.SocketConnectionBase - Backend notice: severity='NOTICE', code='00000', message='table "product" does not exist, skipping', detail='null', hint='null', position='null', internalPosition='null', internalQuery='null', where='null', file='tablecmds.c', line='1060', routine='DropErrorMsgNonExistent', schema='null', table='null', column='null', dataType='null', constraint='null'
[Hibernate]
drop sequence if exists hibernate_sequence
2021-05-18 13:54:45,540 WARN [vert.x-eventloop-thread-0] io.vertx.sqlclient.impl.SocketConnectionBase - Backend notice: severity='NOTICE', code='00000', message='sequence "hibernate_sequence" does not exist, skipping', detail='null', hint='null', position='null', internalPosition='null', internalQuery='null', where='null', file='tablecmds.c', line='1060', routine='DropErrorMsgNonExistent', schema='null', table='null', column='null', dataType='null', constraint='null'
[Hibernate]
create sequence hibernate_sequence start 1 increment 1
[Hibernate]
create table Product (
id int8 not null,
name varchar(255),
price numeric(19, 2) not null,
primary key (id)
)
[Hibernate]
alter table if exists Product
add constraint UK_gxubutkbk5o2a6aakbe7q9kww unique (name)
2021-05-18 13:54:45,574 INFO [vert.x-eventloop-thread-0] io.vertx.howtos.hr.MainVerticle - ✅ Hibernate Reactive is ready
2021-05-18 13:54:45,576 INFO [vert.x-eventloop-thread-1] io.vertx.howtos.hr.MainVerticle - ✅ Deployment success
2021-05-18 13:54:45,576 INFO [vert.x-eventloop-thread-1] io.vertx.howtos.hr.MainVerticle - 💡 PostgreSQL container started in 8349ms
2021-05-18 13:54:45,576 INFO [vert.x-eventloop-thread-1] io.vertx.howtos.hr.MainVerticle - 💡 Vert.x app started in 1658ms
We can insert products:
$ http :8080/products name="Baguette" price="1.20"
HTTP/1.1 200 OK
content-length: 39
content-type: application/json; charset=utf-8
{
"id": 1,
"name": "Baguette",
"price": 1.2
}
$ http :8080/products name="Pain" price="1.40"
HTTP/1.1 200 OK
content-length: 35
content-type: application/json; charset=utf-8
{
"id": 2,
"name": "Pain",
"price": 1.4
}
We can also list a specific product:
$ http :8080/products/1
HTTP/1.1 200 OK
content-length: 39
content-type: application/json; charset=utf-8
{
"id": 1,
"name": "Baguette",
"price": 1.2
}
And we can of course list all products:
$ http :8080/products
HTTP/1.1 200 OK
content-length: 77
content-type: application/json; charset=utf-8
[
{
"id": 1,
"name": "Baguette",
"price": 1.2
},
{
"id": 2,
"name": "Pain",
"price": 1.4
}
]
Since we enabled Hibernate SQL logging, the application logs show the requests being executed such as:
[Hibernate]
select
product0_.id as id1_0_0_,
product0_.name as name2_0_0_,
product0_.price as price3_0_0_
from
Product product0_
where
product0_.id=$1
-
We built a Vert.x service with a HTTP API that accesses a database using a Vert.x reactive driver and Hibernate Reactive.
-
We used the familiar object-relational mapping programming model while still having end-to-end reactive requests processing.
-
TestContainers can be used to easily assemble self-contained demo applications.