Data Persistence with Quarkus and Hibernate Panache

In this article we will learn how to simplify Data Persistence with Quarkus, using Hibernate ORM Panache a library that stands on the top of Hibernate.

Bootstrap a Quarkus project with Panache

Let’s start creating a Quarkus project which includes the following extensions:

mvn io.quarkus:quarkus-maven-plugin:2.3.0.Final:create \
     -DprojectGroupId=com.mastertheboss \
     -DprojectArtifactId=panache-demo \
     -DclassName="com.mastertheboss.MyService" \
     -Dpath="/tickets" \
     -Dextensions="quarkus-hibernate-orm-panache,quarkus-jdbc-postgresql,quarkus-resteasy-jsonb"
If you prefer, you can use Quarkus CLI to create your projects. Read more here: What’s new with Quarkus 2.0 and how to get started quickly

As you can see, we have included a set of additional extensions that will be needed to create our application:

Adding extension io.quarkus:quarkus-jdbc-postgresql
Adding extension io.quarkus:quarkus-hibernate-orm-panache
Adding extension io.quarkus:quarkus-resteasy-jsonb

So, you should have the following dependencies in your project.

 <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-bom</artifactId>
        <version>${quarkus.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy</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>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-jdbc-postgresql</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy-jsonb</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-hibernate-orm-panache</artifactId>
    </dependency>

Hibernate Panache is an addition to Hibernate which makes faster to write the ORM layer for your applications. There are two strategies for plugging Panache in your Entity:

  • Extending the class io.quarkus.hibernate.orm.panache.PanacheEntity : This is the simplest option as you will get an ID field that is auto-generated.
  • Extending io.quarkus.hibernate.orm.panache.PanacheEntityBase : This option can be used if you require a custom ID strategy.

In the following Entity we will use the former strategy, that is extending the class io.quarkus.hibernate.orm.panache.PanacheEntity:

import javax.persistence.Column;
import javax.persistence.Entity;
import io.quarkus.hibernate.orm.panache.PanacheEntity;

@Entity
public class Ticket extends PanacheEntity {

    @Column(length = 20, unique = true)
    public String name;

    @Column(length = 3, unique = true)
    public String seat;

    public Ticket() {
    }

    public Ticket(String name, String seat) {
        this.name = name;
        this.seat = seat;
    }
}

When extending the class io.quarkus.hibernate.orm.panache.PanacheEntity you will be able to use out of the box some benefits like an ID field that is auto-generated. You can still choose a custom ID strategy, by extending the PanacheEntityBase instead and handle the ID yourself.

Also, Panache uses public Class variables for your fields hence there is no need to write boilerplate getters and setters. You can directly refer to the field name. Let’s now build an example application which uses the above Entity class.

Next, edit the class com.mastertheboss.MyService to include the required methods to perform CRUD operations on our Entity:

import java.util.List;

import javax.enterprise.context.ApplicationScoped;
import javax.json.Json;
import javax.transaction.Transactional;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

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

import io.quarkus.panache.common.Sort;

@Path("tickets")
@ApplicationScoped
@Produces("application/json")
@Consumes("application/json")
public class MyService {

    @GET
    public List<Ticket> get() {
        return Ticket.listAll(Sort.by("name"));
    }

    @GET
    @Path("{id}")
    public Ticket getSingle(@PathParam Long id) {
        Ticket entity = Ticket.findById(id);
        if (entity == null) {
            throw new WebApplicationException("Ticket with id of " + id + " does not exist.", 404);
        }
        return entity;
    }

    @POST
    @Transactional
    public Response create(Ticket ticket) {
        if (ticket.id != null) {
            throw new WebApplicationException("Id was invalidly set on request.", 422);
        }

        ticket.persist();
        return Response.ok(ticket).status(201).build();
    }

    @PUT
    @Path("{id}")
    @Transactional
    public Ticket update(@PathParam Long id, Ticket ticket) {
        if (ticket.name == null) {
            throw new WebApplicationException("Ticket Name was not set on request.", 422);
        }

        Ticket entity = Ticket.findById(id);

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

        entity.name = ticket.name;

        return entity;
    }

    @DELETE
    @Path("{id}")
    @Transactional
    public Response delete(@PathParam Long id) {
        Ticket entity = Ticket.findById(id);
        if (entity == null) {
            throw new WebApplicationException("Ticket with id of " + id + " does not exist.", 404);
        }
        entity.delete();
        return Response.status(204).build();
    }

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

        @Override
        public Response toResponse(Exception exception) {
            int code = 500;
            if (exception instanceof WebApplicationException) {
                code = ((WebApplicationException) exception).getResponse().getStatus();
            }
            return Response.status(code)
                    .entity(Json.createObjectBuilder().add("error", exception.getMessage()).add("code", code).build())
                    .build();
        }

    }
}

As you can see, the above Class produces and consumers resoruces as JSON files. The Service will be available at the URI path “/tickets”.

In order to have some default Entities in the Database, we can include a file src/main/resources/import.sql

INSERT INTO ticket(id, name,seat) VALUES (nextval('hibernate_sequence'), 'Phantom of the Opera','11A') 
INSERT INTO ticket(id, name,seat) VALUES (nextval('hibernate_sequence'), 'Chorus Line','5B') 
INSERT INTO ticket(id, name,seat) VALUES (nextval('hibernate_sequence'), 'Mamma mia','21A')

Next, as we have included PostgreSQL dependencies, we will start a Docker instance of it:

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

Next, to connect to PostgreSQL, we will configure the file src/main/resources/application.properties to include the Connection Pool to reach the Database:

quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=quarkus
quarkus.datasource.password=quarkus
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost/quarkusdb
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.log.sql=true

Finally, we will be editing the MyServiceTest class to check that the three Entity have been inserted:

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.containsString;

import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;

@QuarkusTest
public class MyServiceTest {

    @Test
    public void testListAllTickets() {
        given()
                .when().get("/tickets")
                .then()
                .statusCode(200)
                .body(
                        containsString("Phantom of the Opera"),
                        containsString("Chorus Line"),
                        containsString("Mamma mia")
                );

    }

}

Test your Project

You can compile/test the application with:

$ mvn install


2019-05-24 12:00:43,576 INFO  [io.qua.dep.QuarkusAugmentor] (main) Beginning quarkus augmentation
2019-05-24 12:00:44,538 INFO  [io.qua.dep.QuarkusAugmentor] (main) Quarkus augmentation completed in 962ms
Hibernate: 
    
    drop table if exists Ticket cascade
Hibernate: 
    
    drop sequence if exists hibernate_sequence
Hibernate: create sequence hibernate_sequence start 1 increment 1
Hibernate: 
    
    create table Ticket (
       id int8 not null,
        name varchar(20),
        seat varchar(3),
        primary key (id)
    )
Hibernate: 
    
    alter table if exists Ticket 
       add constraint UK_18i99euw9b8fmairpxnxoj10j unique (name)
Hibernate: 
    
    alter table if exists Ticket 
       add constraint UK_r55ptntdg7v0d3249djuu1037 unique (seat)
Hibernate: 
    INSERT INTO ticket(id, name,seat) VALUES (nextval('hibernate_sequence'), 'Phantom of the Opera','11A')
Hibernate: 
    INSERT INTO ticket(id, name,seat) VALUES (nextval('hibernate_sequence'), 'Chorus Line','5B')
Hibernate: 
    INSERT INTO ticket(id, name,seat) VALUES (nextval('hibernate_sequence'), 'Mamma mia','21A')

On the other hand, to execute the application as Java application:

$ mvn quarkus:dev

You can test the application with:

$ curl http://localhost:8080/tickets

[

    {
        "persistent": true,
        "id": 2,
        "name": "Chorus Line",
        "seat": "5B"
    },
    {
        "persistent": true,
        "id": 3,
        "name": "Mamma mia",
        "seat": "21A"
    },
    {
        "persistent": true,
        "id": 1,
        "name": "Phantom of the Opera",
        "seat": "11A"
    }

]

Conclusion

In this tutorial we have covered how to build a basic CRUD application with Quarkus, Hibernate panache and PostgreSQL. You can refer to this tutorial to learn how to compile the same application to native code: Getting started with QuarkusIO

You can checkout the full source code for this tutorial at: https://github.com/fmarchioni/mastertheboss/tree/master/quarkus/panache-demo

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