Getting started with Quarkus and Hibernate

This short tutorial shows how to create a Quarkus application which exposes a set of JAX-RS endpoints which are backed by a repository of data managed with JPA:

Create a basic Quarkus project first:

mvn io.quarkus:quarkus-maven-plugin:1.4.2.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=quarkus-hibernate \
    -DclassName="org.acme.ExampleResource" \
    -Dpath="/customer"

Depending on the Database you will use, you need to include the appropriate JDBC Driver. Here is the full list of dependencies you will need to run/test your application with hibernate-orm and PostgreSQL:

<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-resteasy</artifactId>
</dependency>
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-hibernate-orm</artifactId>
</dependency>
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-resteasy-jsonb</artifactId>
</dependency>
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-jdbc-postgresql</artifactId>
</dependency>    
<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>

Notice we have included also JSON-B dependency. That is required as our Endpoint consumes and produces JSON data. Here is the Endpoint Class which contains CRUD methods coupled with GET/POST/PUT/DELETE HTTP methods:

package org.acme;

import org.jboss.resteasy.annotations.jaxrs.PathParam;

import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.transaction.Transactional;
import javax.ws.rs.*;
import javax.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();
    }
}

Now 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 javax.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 + '\'' +
                '}';
    }
}

Finally, the configuration of the Database connection. The standard configuration file for Quarkus is placed in 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.log.sql=true
quarkus.hibernate-orm.sql-load-script=import.sql

Please notice that the Hibernate properties can also be specified 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>
            <!-- Connection specific -->
            <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQL95Dialect"/>

            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>

            <!--
                Optimistically create the tables;
                will cause background errors being logged if they already exist,
                but is practical to retain existing data across runs (or create as needed) -->
            <property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/>

            <property name="javax.persistence.validation.mode" value="NONE"/>
        </properties>

    </persistence-unit>
</persistence>


We have included 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

Now, before starting the application, make sure PostgreSQL Database is available:

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:10.5  

Now we can start the Quarkus application in development mode:

mvn clean install quarkus:dev
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2020-05-17 13:27:06,932 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')
2020-05-17 13:27:07,341 INFO  [io.quarkus] (Quarkus Main Thread) quarkus-hibernate /hello (powered by Quarkus 1.4.2.Final) started in 2.054s. Listening on: http://0.0.0.0:8080
2020-05-17 13:27:07,343 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2020-05-17 13:27:07,343 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [agroal, cdi, hibernate-orm, jdbc-postgresql, narayana-jta, resteasy, resteasy-jsonb]

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"
  }
]

Let’s 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"}

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

In this tutorial we have manually configured and started the PostgreSQL database to run this example. If you are using Quarkus 1.13 (or newer) you can leverage the DevServices feature that let you develop or test Database application with zero configuration. In a nutshell, Quarkus will detect the Database to be used from the extensions added in pom.xml, pull the container image of the Database and set up the connection in your application. 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