Getting started with Hibernate reactive on Quarkus

This tutorial will introduce you to Hibernate Reactive which enables support for non-blocking database drivers and a reactive programming with Hibernate ORM.

Uni and Multi streams

Persistence operations are designed to use blocking IO for interaction with the database, and are therefore not appropriate for use in a reactive environment. Hibernate Reactive is the first ORM implementation which is able to take advantage of non-blocking database clients

Hibernate Reactive works on the top of reactive programming environment like therefore you should be familiar with the concept of Reactive Streams.

Reactive Streams is an initiative to provide a standard for asynchronous stream processing with non-blocking back pressure on the JVM

Reactive Streams are implemented in the Mutiny framework which provides a variant of Vert.x streams, adapting Vert.x back pressure protocol to Reactive Streams. can wrapped by a chain of Java CompletionStages o Mutiny Uni/Multi streams.

Mutiny offers two types that are both event-driven and lazy:

  • A Uni emits a single event (an item or a failure). A good example is the result of sending a message to a message broker queue.
  • A Multi emits multiple events (n items, 1 failure or 1 completion). A good example is receiving messages from a message broker queue.

Using a very simplistic pattern, you can observe events using the following asynchronous pattern:

onItem().call(item → someAsyncAction(item))

Conversely, we can handle failure events with the following pattern:

Uni<String> uni1 = …
uni1
.onFailure().recoverWithItem(“my fallback value”);

In the following sections, we will show how to apply the above patterns in a sample Hibernate Reactive application which takes advantage of Uni streams to return Database object in non-blocking style.

Please note that Hibernate reactive API can be applied both to standard Hibernate ORM and Panache. In this tutorial we will apply reactive streams the "classic" Hibernate ORM

Creating a Quarkus Hibernate reactive application

To kickstart an hibernate Reactive application, you need to fulfill the following requirements:

  1. Include Quarkus’ quarkus-hibernate-reactive dependency, instead of the default quarkus-hibernate-orm
  2. Use a JDBC Driver which is compliant with Vert.x API. For example, quarkus-reactive-pg-client (PostgreSQL)
  3. Include resteasy’s reactive dependencies
  4. Adjust your endpoints to return io.smallrye.mutiny.Uni or io.smallrye.mutiny.Multi

Let’s see this in action. We will bootstrap a basic Quarkus project as follows:

mvn io.quarkus.platform:quarkus-maven-plugin:2.4.1.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=hibernate-reactive \
    -DprojectVersion=1.0.0 \
    -DclassName="org.acme.ExampleResource"

Then, move into the folder hibernate-reactive and add the following extensions:

mvn quarkus:add-extension -Dextensions="quarkus-hibernate-reactive,quarkus-reactive-pg-client,quarkus-resteasy-reactive,quarkus-resteasy-reactive-jackson"

Ok, so far so good. We will now begin our coding.

Our Entity class is not different from the standard Hibernate ORM / JPA Entity:

@Entity
@Table
@NamedQuery(name = "Customers.findAll", query = "SELECT c FROM Customer c ORDER BY c.name")
public class Customer {

    @Id
    @SequenceGenerator(name = "customersSequence", sequenceName = "known_customers_id_seq", allocationSize = 1, initialValue = 10)
    @GeneratedValue(generator = "customersSequence")
    private Integer id;

    @Column()
    private String name;

    public Customer() {
    }

    public Customer(String name) {
        this.name = name;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
 
}

You can manipulate the Customer Entity through a REST Endpoint. As a comparison, check this article which uses the standard Hibernate ORM application in a Quarkus application: Getting started with Quarkus and Hibernate

Here is the REST Reactive Endpoint:

@Path("customers")
@ApplicationScoped
@Produces("application/json")
@Consumes("application/json")
public class ExampleResource {

    private static final Logger LOGGER = Logger.getLogger(ExampleResource.class);

    @Inject
    Mutiny.SessionFactory sf;

    @GET
    public Uni<List<Customer>> get() {
        return sf.withTransaction((s,t) -> s
                .createNamedQuery("Customers.findAll", Customer.class)
                .getResultList()
        );
    }

    @GET
    @Path("{id}")
    public Uni<Customer> getSingle(@RestPath Integer id) {
        return sf.withTransaction((s,t) -> s.find(Customer.class, id));
    }

    @POST
    public Uni<Response> create(Customer customer) {
        if (customer == null || customer.getId() != null) {
            throw new WebApplicationException("Id was invalidly set on request.", 422);
        }

        return sf.withTransaction((s,t) -> s.persist(customer))
                .replaceWith(() -> Response.ok(customer).status(CREATED).build());
    }

    @PUT
    @Path("{id}")
    public Uni<Response> update(@RestPath Integer id, Customer customer) {
        if (customer == null || customer.getName() == null) {
            throw new WebApplicationException("Fruit name was not set on request.", 422);
        }

        return sf.withTransaction((s,t) -> s.find(Customer.class, id)
            // If entity exists then update it
            .onItem().ifNotNull().invoke(entity -> entity.setName(customer.getName()))
            .onItem().ifNotNull().transform(entity -> Response.ok(entity).build())
            // If entity not found return the appropriate response
            .onItem().ifNull()
            .continueWith(() -> Response.ok().status(NOT_FOUND).build() )
        );
    }

    @DELETE
    @Path("{id}")
    public Uni<Response> delete(@RestPath Integer id) {
        return sf.withTransaction((s,t) ->
                s.find(Customer.class, id)
                    // If entity exists then delete it
                    .onItem().ifNotNull()
                        .transformToUni(entity -> s.remove(entity)
                                .replaceWith(() -> Response.ok().status(NO_CONTENT).build()))
                // If entity not found return the appropriate response
                .onItem().ifNull().continueWith(() -> Response.ok().status(NOT_FOUND).build()));
    }
}

Firstly, as you can see, when we write persistence logic using Hibernate Reactive, we’ll be working with a reactive Mutiny SessionFactory. Most operations of this interface are non-blocking, and execution of SQL against the database is never performed synchronously.

To obtain a reactive Session from the SessionFactory, you can use withSession(). For extra convenience, there’s a method withTransaction that opens a session and starts a transaction in one call:

 public Uni<List<Customer>> get() {
        return sf.withTransaction((s,t) -> s
                .createNamedQuery("Customers.findAll", Customer.class)
                .getResultList()
        );
}

Please note that the session is automatically flushed at the end of the transaction..

As you can see, Session interface has methods with the same names as methods of the JPA EntityManager. (ex. find(), persist(), createNamedQuery() etc) . Therefore it will be simple to map your synchronous JPA statements into the reactive ones.

it is worth mentioning that you can decorate your Endpoint with an ExceptionMapper class which creates an HTTP Response of the Exception in JSON Format:

@Provider
public static class ErrorMapper implements ExceptionMapper<Exception> {

    @Inject
    ObjectMapper objectMapper;

    @Override
    public Response toResponse(Exception exception) {
        LOGGER.error("Failed to handle request", exception);

        int code = 500;
        if (exception instanceof WebApplicationException) {
            code = ((WebApplicationException) exception).getResponse().getStatus();
        }

        ObjectNode exceptionJson = objectMapper.createObjectNode();
        exceptionJson.put("exceptionType", exception.getClass().getName());
        exceptionJson.put("code", code);

        if (exception.getMessage() != null) {
            exceptionJson.put("error", exception.getMessage());
        }

        return Response.status(code)
                .entity(exceptionJson)
                .build();
    }

}

To connect to a database, we will set up the following datasource properties in application.properties:

%prod.quarkus.datasource.db-kind=postgresql
%prod.quarkus.datasource.username=quarkus_test
%prod.quarkus.datasource.password=quarkus_test

quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.log.sql=true
quarkus.hibernate-orm.sql-load-script=import.sql

%prod.quarkus.datasource.reactive.url=vertx-reactive:postgresql://localhost/quarkus_test

Notice we are using a Vert.x reactive PostgreSQL URL which differs from the default PostgreSQL JDBC URL.

Testing the Quarkus application

To test the application we need to have a PostgreSQL database up and running. You can start it with docker as follows:

docker run -it --rm=true --name quarkus_test -e POSTGRES_USER=quarkus_test -e POSTGRES_PASSWORD=quarkus_test -e POSTGRES_DB=quarkus_test -p 5432:5432 postgres

Next, build the Quarkus application:

mvn install

After that, you can run the application in the “prod” profile as follows:

java -jar ./target/quarkus-app/quarkus-run.jar

Then, test the application with curl. For example, to list all customers:

curl -s http://localhost:8080/customers | jq

Here is the output from the REST GET:

[
   {
      "id":1,
      "name":"Batman"
   },
   {
      "id":2,
      "name":"Superman"
   },
   {
      "id":3,
      "name":"Wonder woman"
   }
]

You can go on testing the other CRUD methods, for example to add a Customer:

curl -X POST http://localhost:8080/customers -H 'Content-Type: application/json' -d '{"name":"Antman"}'

Source code

The source code for this example application (derived from Quarkus’ quickstart application) is available on Github at: https://github.com/fmarchioni/mastertheboss/tree/master/quarkus/hibernate-reactive

Found the article helpful? if so please follow us on Socials