This tutorial shows how to handle Data Persistence with Quarkus, using Hibernate panache a library that stands on the top of Hibernate

Hibernate Panache is an addition to Hibernate which makes faster to write the ORM layer for your applications. Let's see a quick example to understand the differences with Hibernate:

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;
    }
}

As you can see, the Entity extends the class io.quarkus.hibernate.orm.panache.PanacheEntity. This Class provides 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.

Start the Quarkus Maven plugin with:

mvn io.quarkus:quarkus-maven-plugin:0.13.1:create \
     -DprojectGroupId=com.mastertheboss \
     -DprojectArtifactId=hibernate-demo \
     -DclassName="com.mastertheboss.MyService" \
     -Dpath="/tickets" \
     -Dextensions="quarkus-hibernate-orm-panache,quarkus-jdbc-postgresql,quarkus-resteasy-jsonb"

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>

Now include the Ticket.java class in your src/main/java/com/mastertheboss folder.

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 -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=postgres -e POSTGRES_USER=postgres -d -p 5432:5432 postgres:9.4 

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

quarkus.datasource.url=jdbc:postgresql:postgres
quarkus.datasource.driver=org.postgresql.Driver
quarkus.datasource.username=postgres
quarkus.datasource.password=postgres
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.log.sql=true

We are almost done. Last thing 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")
                );

    }

}

You can compile/test the application with:

$ mvn package


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 compile 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 and PostgreSQL. You can refer to this tutorial to learn how to compile the same application to native code: Getting started with Quarkus

0
0
0
s2smodern