Getting started with Quarkus and Hibernate

In this article we will learn how to create and run a sample Quarkus 3 application which uses Hibernate ORM and JPA. We will create a sample REST Endpoint to expose the basic CRUD operations against a relational Database such as PostgreSQL.

Create the Quarkus Project

Firstly, create a basic Quarkus project first. For example, using https://code.quarkus.io

quarkus hibernate tutorial

Then, unzip the project in a folder of your Drive. Since this application will be using PostgreSQL from Hibernate, the following list of dependencies will be needed in your pom.xml file:

<dependencies>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-hibernate-orm</artifactId>
    </dependency>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-agroal</artifactId>
    </dependency>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-resteasy-reactive</artifactId>
    </dependency>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-resteasy-reactive-jackson</artifactId>
    </dependency>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-jdbc-postgresql</artifactId>
    </dependency>

    <!-- Testing: -->
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-junit5</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>io.rest-assured</groupId>
        <artifactId>rest-assured</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

Coding the Hibernate example

Firstly, to allow accessing our application from HTTP Clients, we will modify the default Endpoint to include a set of CRUD methods which will match with the GET/POST/PUT/DELETE HTTP methods:

package org.acme;

import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.Response;

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

    @Inject
    EntityManager entityManager;

    @GET
    public Customer[] get() {
        return entityManager.createNamedQuery("Customers.findAll", Customer.class)
                .getResultList().toArray(new Customer[0]);
    }
    @POST
    @Transactional
    public Response create(Customer customer) {
        if (customer.getId() != null) {
            throw new WebApplicationException("Id was invalidly set on request.", 422);
        }
        System.out.println("Creating "+customer);
        entityManager.persist(customer);
        return Response.ok(customer).status(201).build();
    }

    @PUT
    @Transactional
    public Customer update(Customer customer) {
        if (customer.getId() == null) {
            throw new WebApplicationException("Customer Id was not set on request.", 422);
        }

        Customer entity = entityManager.find(Customer.class, customer.getId());

        if (entity == null) {
            throw new WebApplicationException("Customer with id of " + customer.getId() + " does not exist.", 404);
        }

        entity.setName(customer.getName());

        return entity;
    }

    @DELETE
    @Transactional
    public Response delete(Customer customer) {
        Customer entity = entityManager.find(Customer.class, customer.getId());
        if (entity == null) {
            throw new WebApplicationException("Customer with id of " + customer.getId() + " does not exist.", 404);
        }
        entityManager.remove(entity);
        return Response.status(204).build();
    }
}

Next, we will code the Entity class. This class includes an example of @NamedQuery to load the list of Customer objects and the definition of a Sequence which will generate the Customer id. As we will initially add some Customer records with the import.sql script, we will set as initialValue 10:

package org.acme;

import jakarta.persistence.*;

@Entity
@NamedQuery(name = "Customers.findAll", query = "SELECT c FROM Customer c ORDER BY c.name")
public class Customer {
    private Long id;
    private String name;

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "customerSequence")
    @SequenceGenerator(name = "customerSequence", sequenceName = "customerSeq", allocationSize = 1, initialValue = 10)
    public Long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Customer{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

Configuring the Database Connection

Finally, the configuration of the Database connection. If you want to opt for Quarkus Dev Services for Database, the following META-INF/application.properties configuration will be sufficient to trigger a PostgreSQL Docker image automatically:

quarkus.datasource.db-kind=postgresql
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.sql-load-script=import.sql

On the other hand, if you want to start yourself the Database instance, then you can include the username and password along with the JDBC URL in the file META-INF/application.properties :

quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=quarkus
quarkus.datasource.password=quarkus
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost/quarkusdb
quarkus.datasource.jdbc.max-size=8
quarkus.datasource.jdbc.min-size=2

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

Please notice that you can also specify the Hibernate properties through the META-INF/persistence.xml file. This is especially useful if you are migrating from a Java EE application:

<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
             http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
             version="2.1">

    <persistence-unit name="CustomerPU" transaction-type="JTA">

        <description>My customer entities</description>

        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQL95Dialect"/>

            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/>
            <property name="javax.persistence.validation.mode" value="NONE"/>
        </properties>

    </persistence-unit>
</persistence>

Then, we will include an import.sql file to bootstrap the application with 3 Customer objects:

INSERT INTO Customer(id, name) VALUES (1, 'Batman');
INSERT INTO Customer(id, name) VALUES (2, 'Superman');
INSERT INTO Customer(id, name) VALUES (3, 'Wonder woman');

Here is the full structure of our application:

src
├── main
│   ├── docker
│   │   ├── Dockerfile.jvm
│   │   └── Dockerfile.native
│   ├── java
│   │   └── org
│   │       └── acme
│   │           ├── Customer.java
│   │           └── ExampleResource.java
│   └── resources
│       ├── application.properties
│       ├── import.sql
│       └── META-INF
│           └── resources
│               └── index.html
├── README.md
└── test
    └── java
        └── org
            └── acme
                ├── CustomerEndpointTest.java
                └── NativeExampleResourceIT.java

Testing the application

If you are using Quarkus Dev Service you are ready to go !

On the other habd, if you decided to include the Database settings yourself, then make sure you start PostgreSQL first:

docker run --ulimit memlock=-1:-1 -it --rm=true --memory-swappiness=0 --name quarkus_test -e POSTGRES_USER=quarkus -e POSTGRES_PASSWORD=quarkus -e POSTGRES_DB=quarkusdb  -p 5432:5432 postgres:13  

Next, we can start the Quarkus application in development mode:

$ mvn install quarkus:dev

 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2022-12-19 10:32:31,120 INFO  [io.agr.pool] (Quarkus Main Thread) Datasource '<default>': Initial size smaller than min. Connections will be created when necessary

Hibernate: 
    
    drop table if exists Customer cascade
Hibernate: 
    
    drop sequence if exists customerSeq
Hibernate: create sequence customerSeq start 10 increment 1
Hibernate: 
    
    create table Customer (
       id int8 not null,
        name varchar(255),
        primary key (id)
    )

Hibernate: 
    INSERT INTO Customer(id, name) VALUES (1, 'Batman')
Hibernate: 
    INSERT INTO Customer(id, name) VALUES (2, 'Superman')
Hibernate: 
    INSERT INTO Customer(id, name) VALUES (3, 'Wonder woman')
2022-12-19 10:32:31,472 INFO  [io.quarkus] (Quarkus Main Thread) quarkus-hibernate 2.15 on JVM (powered by Quarkus 2.15.0.Final) started in 2.165s. Listening on: http://localhost:8080
2022-12-19 10:32:31,472 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2022-12-19 10:32:31,473 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [agroal, cdi, hibernate-orm, jdbc-postgresql, narayana-jta, resteasy-reactive, resteasy-reactive-jackson, smallrye-context-propagation, vertx]

Let’s check at first the list of Customer objects:

curl -s http://localhost:8080/customer | jq
[
  {
    "id": 1,
    "name": "Batman"
  },
  {
    "id": 2,
    "name": "Superman"
  },
  {
    "id": 3,
    "name": "Wonder woman"
  }
]

Next, use the POST method to insert a new Customer:

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

{"id":10,"name":"Spiderman"}

Let’s use the PUT method to modify an existing Customer:

curl -d '{"id":10,"name":"Hulk"}' -H "Content-Type: application/json" -X PUT http://localhost:8080/customer

{"id":10,"name":"Hulk"}

Finally, let’s use the DELETE method to delete an existing Customer:

curl -d '{"id":10,"name":"Hulk"}' -H "Content-Type: application/json" -X DELETE http://localhost:8080/customer

{"id":10,"name":"Hulk"}

Let’s check again the list of Customer objects:

curl -s http://localhost:8080/customer | jq
[
  {
    "id": 1,
    "name": "Batman"
  },
  {
    "id": 2,
    "name": "Superman"
  },
  {
    "id": 3,
    "name": "Wonder woman"
  }
]

Great! We have just learnt how to design a basic CRUD application using Quarkus, JPA and JAX-RS.

Conclusion

In this tutorial we have discussed how to run an Hibernate ORM Application using JPA API and Quarkus. We also have demonstrated how to use Quarkus Dev Services for Database. If you want to read more about it, check this tutorial: Zero Config Database configuration with Quarkus (DevServices)

Source code: https://github.com/fmarchioni/mastertheboss/tree/master/quarkus/hibernate