Quarkus Inner-loop

The code

Our simple application exposes a CRUD interface for a table (FRUIT) in a relational database by using JPA and Hibernate as the ORM framework. To do so, it uses JPA annotations like @Entity, @Id, etc. You can find the Java code of the JPA entity below.

In order to make the code more portable we have annotated the id attribute so that the value is generated using a sequence named FRUIT_SEQ. Our @Entity extends PanacheEntityBase to be override the by default ID generation mechanism. Note how simplified the code is, the only method there is a custom query getAllFruitsForSeason, all the usual CRUD functions are implemented in PanacheEntityBase.

@Entity
public class Fruit extends PanacheEntityBase {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "FruitSequence")
    @SequenceGenerator(name = "FruitSequence", sequenceName = "FRUIT_SEQ")
    public Integer id;
    public String name;
    public String season;

    public static List<Fruit> getAllFruitsForSeason(String season) {
        return find("season", season).list();
    }
}

The actual REST service is implemented in class FruitResource find below a simplified version of the code. Nothing complicated here, just using the inherited methods of the Fruit entity si enough.

@Path("/fruit")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class FruitResource {
    Logger logger = Logger.getLogger(FruitResource.class);

    @ConfigProperty(name = "hello.message")
    String message;

    @GET
    @Path("hello")
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        logger.debug("Hello method is called with message: " + this.message); // logging & custom property
        return message; // custom property
    }

    @GET
    public List<Fruit> allFruits() {
        return Fruit.listAll();
    }

    @GET
    @Path("{season}")
    public List<Fruit> fruitsBySeason(@PathParam("season") String season) {
        return Fruit.getAllFruitsForSeason(season);
    }

    @POST
    @Transactional
    public Response saveFruit(Fruit fruit) {
        // since the FruitEntity is a panache entity
        // persist is available by default
        fruit.persist();
        final URI createdUri = UriBuilder.fromResource(FruitResource.class)
                        .path(Long.toString(fruit.id))
                        .build();
        return Response.created(createdUri).build();
    }
}

Getting the Code

Time to get our hands dirty. Clone or download the code, your choice.

  • Clone the code

  • Download the code

git clone --single-branch --branch master https://github.com/redhat-scholars/java-inner-loop-dev-guide.git
curl -L https://github.com/redhat-scholars/java-inner-loop-dev-guide/archive/master.zip -o java-inner-loop-dev-guide.zip
unzip java-inner-loop-dev-guide.zip
mv java-inner-loop-dev-guide-master java-inner-loop-dev-guide

Change to the dir where the code is.

cd java-inner-loop-dev-guide/apps/quarkus-app

Quarkus Profiles [TODO!!!!]

As we stated at the beginning we want to use different databases, H2 to develop quickly then either PostgreSQL or Oracle once the code is deployed in OpenShift. In order to do so we have defined 3 profiles:

  • local for H2

  • openshift-postgresql for PostgreSQL

  • openshift-oracle for Oracle

  • local

  • openshift-postgresql

  • openshift-oracle

# H2 settings
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.datasource.platform=h2

spring.jpa.properties.hibernate.hbm2ddl.import_files=import-h2.sql
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create
# PostgreSQL settings
spring.datasource.url=jdbc:postgresql://${SERVICE_DB_HOST}:5432/${SERVICE_DB_NAME}
spring.datasource.username=${SERVICE_DB_USER}
spring.datasource.password=${SERVICE_DB_PASSWORD}
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.platform=postgresql

spring.jpa.properties.hibernate.hbm2ddl.import_files=import-postgresql.sql
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=create

## To avoid CLOB related error...
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
# Oracle settings
spring.datasource.url=jdbc:oracle:thin:@${SERVICE_DB_HOST}:1521/${SERVICE_DB_NAME}
spring.datasource.username=${SERVICE_DB_USER}
spring.datasource.password=${SERVICE_DB_PASSWORD}
spring.datasource.driver.class=oracle.jdbc.driver.OracleDriver
spring.datasource.platform=oracle

spring.jpa.properties.hibernate.hbm2ddl.import_files=import-oracle.sql
spring.jpa.database-platform=org.hibernate.dialect.Oracle12cDialect
spring.jpa.hibernate.ddl-auto=create

There are, accordingly, three different import.sql files, since each database has a different way of referring to the next value of a database sequence.

  • import-h2.sql

  • import-postgresql.sql

  • import-oracle.sql

insert into fruit (id, name) values (FRUIT_SEQ.NEXTVAL, 'Cherry');
insert into fruit (id, name) values (FRUIT_SEQ.NEXTVAL, 'Apple');
insert into fruit (id, name) values (FRUIT_SEQ.NEXTVAL, 'Banana');
insert into fruit (id, name) values ( nextval ('FRUIT_SEQ'), 'Cherry');
insert into fruit (id, name) values ( nextval ('FRUIT_SEQ'), 'Apple');
insert into fruit (id, name) values ( nextval ('FRUIT_SEQ'), 'Banana');
insert into fruit (id, name) values (FRUIT_SEQ.NEXTVAL, 'Cherry');
insert into fruit (id, name) values (FRUIT_SEQ.NEXTVAL, 'Apple');
insert into fruit (id, name) values (FRUIT_SEQ.NEXTVAL, 'Banana');

Running the code locally against H2

One decision made for this example was to keep it as SQL-standard as possible so that we can move to different database as easily as possible. I know, sometimes this is not possible or ideal…​ but it’s ok for this example.

As we have said before in this guide you’ll not have to code, instead we’ll focus on running, testing, deploying, etc.

Let’s get started and see if the code works locally and using H2.

With mvn quarkus:dev we’re starting Quarkus in dev mode, this means %dev profile is active and also that live code reloading is active.
mvn quarkus:dev

You should get this output.

Note all the Hibernate traces like drop table if exists Fruit CASCADE and also pay attention to the profile in use ⇒ Profile dev activated. Live Coding activated.

...
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 5 resources
[INFO] Nothing to compile - all classes are up to date
Listening for transport dt_socket at address: 5005
[INFO] Checking for existing resources in: /Users/redhat-scholars/java-inner-loop-dev-guide/apps/quarkus-app/src/main/kubernetes.
[INFO] Adding existing Secret with name: fruits-db.
Hibernate:

    drop table if exists Fruit CASCADE
Hibernate:

    drop sequence if exists FRUIT_SEQ
Hibernate: create sequence FRUIT_SEQ start with 1 increment by 50
Hibernate:

    create table Fruit (
       id integer not null,
        name varchar(255),
        season varchar(255),
        primary key (id)
    )
Hibernate:
    insert into fruit (id,name,season) VALUES ( FRUIT_SEQ.NEXTVAL , 'Mango'      , 'Spring' )
Hibernate:
    insert into fruit (id,name,season) VALUES ( FRUIT_SEQ.NEXTVAL , 'Strawberry' , 'Spring' )
Hibernate:
    insert into fruit (id,name,season) VALUES ( FRUIT_SEQ.NEXTVAL , 'Orange'     , 'Winter' )
Hibernate:
    insert into fruit (id,name,season) VALUES ( FRUIT_SEQ.NEXTVAL , 'GrapeFruit' , 'Winter' )
Hibernate:
    insert into fruit (id,name,season) VALUES ( FRUIT_SEQ.NEXTVAL , 'Blueberry'  , 'Summer' )
Hibernate:
    insert into fruit (id,name,season) VALUES ( FRUIT_SEQ.NEXTVAL , 'Banana'     , 'Summer' )
Hibernate:
    insert into fruit (id,name,season) VALUES ( FRUIT_SEQ.NEXTVAL , 'Plum'       , 'Summer' )
Hibernate:
    insert into fruit (id,name,season) VALUES ( FRUIT_SEQ.NEXTVAL , 'Apple'      , 'Fall'   )
Hibernate:
    insert into fruit (id,name,season) VALUES ( FRUIT_SEQ.NEXTVAL , 'Grape '     , 'Fall'   )
__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2021-02-08 16:50:38,565 INFO  [io.quarkus] (Quarkus Main Thread) atomic-fruit-service 1.0-SNAPSHOT on JVM (powered by Quarkus 1.9.2.Final) started in 3.348s. Listening on: http://0.0.0.0:8080
2021-02-08 16:50:38,573 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2021-02-08 16:50:38,573 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [agroal, cdi, hibernate-orm, hibernate-orm-panache, jdbc-h2, jdbc-postgresql, kubernetes, mutiny, narayana-jta, resteasy, resteasy-jsonb, smallrye-context-propagation, smallrye-health, smallrye-openapi, swagger-ui]

Now open a browser and point to http://localhost:8080

You can edit, save, delete to test the functionalities implemented by FruitResource

You should see this:

Fruit Service on H2 - Quarkus