Quarkus project setup for hibernate-orm module

After testing Hibernate and the Stateless Session I wanted to know what is supported out of the box in Quarkus with the Jakarta Persistence api.

The best start page I could find when using Java and Maven is: https://quarkus.io/guides/maven-tooling#build-tool-maven

First create a new maven module as described:

mvn io.quarkus.platform:quarkus-maven-plugin:2.15.0.Final:create -DprojectGroupId=nl.escay -DprojectArtifactId=quarkus.hibernate.stateless.session

I updated the pom.xml to use version 3.0.0.Alpha6 which allows me to test the latest Hibernate. (I was assuming Hibernate 6 was already integrated based on an old Quarkus 3 blog, but at this moment Hibernate 5.6 is integrated)

<quarkus.platform.version>3.0.0.Alpha2</quarkus.platform.version>

Then build it.

mvn install

Then run it.

  • Make sure JAVA_HOME is set (C:\jdk-19.0.1)
  • Make sure PATH is set (C:\jdk-19.0.1\bin)

java -jar ./target/quarkus-app/quarkus-run.jar

E:\quarkus.hibernate.stateless.session>java -jar ./target/quarkus-app/quarkus-run.jar
__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2022-12-21 15:19:19,876 INFO  [io.quarkus] (main) quarkus.hibernate.stateless.session 1.0.0-SNAPSHOT on JVM (powered by Quarkus 3.0.0.Alpha2) started in 1.389s. Listening on: http://0.0.0.0:8080
2022-12-21 15:19:19,954 INFO  [io.quarkus] (main) Profile prod activated.
2022-12-21 15:19:19,955 INFO  [io.quarkus] (main) Installed features: [cdi, resteasy-reactive, smallrye-context-propagation, vertx]

Open browser:

  • localhost:8080 -> index.html page is shown
  • localhost:8080/hello -> REST webservice response is shown

CTRL+C to stop the server.

Import maven project in your IDE and you can run the Unit test:

nl.escay.GreetingResourceTest

Output:

dec. 21, 2022 3:30:27 P.M. io.quarkus.bootstrap.runner.Timing printStartupTime
INFO: quarkus.hibernate.stateless.session 1.0.0-SNAPSHOT on JVM (powered by Quarkus 3.0.0.Alpha2) started in 4.500s. Listening on: http://localhost:8081
dec. 21, 2022 3:30:27 P.M. io.quarkus.bootstrap.runner.Timing printStartupTime
INFO: Profile test activated. 
dec. 21, 2022 3:30:27 P.M. io.quarkus.bootstrap.runner.Timing printStartupTime
INFO: Installed features: [cdi, resteasy-reactive, smallrye-context-propagation, vertx]
dec. 21, 2022 3:30:29 P.M. io.quarkus.bootstrap.runner.Timing printStopTime
INFO: quarkus.hibernate.stateless.session stopped in 0.053s

Check which extentions are available:

mvnw quarkus:list-extensions

E:\projects\github\demo-applications\quarkus.hibernate.stateless.session>mvnw quarkus:list-extensions
[INFO] Scanning for projects...
[INFO]
[INFO] ------------< nl.escay:quarkus.hibernate.stateless.session >------------
[INFO] Building quarkus.hibernate.stateless.session 1.0.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- quarkus-maven-plugin:3.0.0.Alpha2:list-extensions (default-cli) @ quarkus.hibernate.stateless.session ---
[INFO] Looking for the newly published extensions in registry.quarkus.io
[INFO] Current Quarkus extensions available:
[INFO]
[INFO] ArtifactId                                         Extension Name
...
[INFO] quarkus-hibernate-envers                           Hibernate Envers
[INFO] quarkus-hibernate-orm                              Hibernate ORM
[INFO] quarkus-hibernate-orm-panache                      Hibernate ORM with Panache
...

Since I want to test some Jakarta persistence I read the following documentation:

Let’s start with only hibernate first.

mvnw quarkus:add-extension -Dextensions=‘hibernate-orm’

On my machine the call fails (perhaps due to using Windows?)

[ERROR] Failed to execute goal io.quarkus.platform:quarkus-maven-plugin:3.0.0.Alpha2:add-extension (default-cli) on project quarkus.hibernate.stateless.session: Unable to update the pom.xml file: Unable to add extensions -> [Help 1]
org.apache.maven.lifecycle.LifecycleExecutionException: Failed to execute goal io.quarkus.platform:quarkus-maven-plugin:3.0.0.Alpha2:add-extension (default-cli) on project quarkus.hibernate.stateless.session: Unable to update the pom.xml file

Solution I used: add dependency manually:

        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-hibernate-orm</artifactId>
        </dependency>

I expected Quarkus 3.0.0.Alpha2 to use Hibernate 6, but I see Hibernate 5 is being used:

[INFO] --- quarkus-maven-plugin:3.0.0.Alpha2:build (default) @ quarkus.hibernate.stateless.session ---
[WARNING] [io.quarkus.agroal.deployment.AgroalProcessor] The Agroal dependency is present but no JDBC datasources have been defined.
[WARNING] [io.quarkus.hibernate.orm.deployment.HibernateOrmProcessor] Hibernate ORM is disabled because no JPA entities were found
[INFO] [org.hibernate.Version] HHH000412: Hibernate ORM core version 5.6.14.Final
[INFO] [io.quarkus.deployment.QuarkusAugmentor] Quarkus augmentation completed in 10341ms

I could not figure out if Hibernate 6 is already integrated, currently it seems to be this issue to implement it: https://github.com/quarkusio/quarkus/issues/26986

For now hibernate-core-jakarta 5.6.14.Final is used and which is also a Jakarta JPA API compatible implementation. See also https://hibernate.org/orm/documentation/getting-started/

Building works. Running ‘mvn install’ shows Quarkus warnings about orm module available but no datasource configured. I added the H2 database driver dependency:

    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-jdbc-h2</artifactId>
    </dependency>

and added a configuration in ‘application.properties’:

# datasource configuration
quarkus.datasource.db-kind = h2
quarkus.datasource.username = sa
quarkus.datasource.password = 
quarkus.datasource.jdbc.url = jdbc:h2:mem:test

# drop and create the database at startup (use `update` to only update the schema)
quarkus.hibernate-orm.database.generation=drop-and-create

# log sql statements (this also enables formatting)
quarkus.hibernate-orm.log.sql=true

Testing Stateless Session code using Jakarta Persistence

Hmm.. there is no StatelessSession equivalent in Jakarta Persistence at this moment #374. Read the paragraph below for a workaround.

To see if non Entity classes can be stored and read into DTOs or Records I continued my test using the available EntityManager.

Inserting non Entity objects can be done using sql like:

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;

@ApplicationScoped
public class PersonService {
    
    @Inject
    EntityManager em; 
	
    @Transactional
    public void createPersonUsingSql(Long id, String firstName, String lastName) {
        em.createNativeQuery("INSERT INTO Person (id, firstName, lastName) VALUES (:id, :firstName, :lastName)")
            .setParameter("id", id)
            .setParameter("firstName", firstName)
            .setParameter("lastName", lastName)
            .executeUpdate();
    }
}

and read objects as DTO or Record like:

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.persistence.EntityManager;

public record PersonRecord(Integer id, String firstName, String lastName) {}

@ApplicationScoped
public class PersonService {
    
    @Inject
    EntityManager em; 
	
    // No transaction needed, you could consider transaction values like NEVER or SUPPORTS
    // @Transactional(value = TxType.NEVER)
    public List<PersonRecord> getPersonsAsRecordUsingTuple() {
        List<Tuple> resultTuples = em.createNativeQuery("select id, firstName, lastName from Person", Tuple.class).getResultList();

        // Convert to Record using stream api, quite similar to Hibernate 6 .setTupleTransformer
        List<PersonRecord> personRecords = resultTuples.stream()
                .map(tuple -> new PersonRecord(
                        (Integer) tuple.get(0), 
                        (String) tuple.get(1), 
                        (String) tuple.get(2)))
                .collect(Collectors.toList());
        
        return personRecords;
    }
}

Using Hibernate StatelessSession in Quarkus via the EntityManager

When using Jakarta Persistence in Quarkus you will know the implementation is using the Hibernate engine. If you don’t mind using Hibernate directly in your code you could use the following approach to still have access to the StatelessSession:

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.StatelessSession;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;

@ApplicationScoped
public class StatelessSessionService {
    
    @Inject
    EntityManager em; 

    @Transactional
    public void createPersonUsingSqlAndStatelessSesssion(Long id, String firstName, String lastName) {
        // If you know Hibernate is the implementation and you do not care about using 
        // Hibernate specific code you can use the Hibernate StatelessSession interface.
        Session hibernateSession = em.unwrap(Session.class);
        SessionFactory sessionFactory = hibernateSession.getSessionFactory();
        
        try (StatelessSession openStatelessSession = sessionFactory.openStatelessSession()) {
            openStatelessSession.createNativeQuery("INSERT INTO Person (id, firstName, lastName) VALUES (:id, :firstName, :lastName)")
                .setParameter("id", id)
                .setParameter("firstName", firstName)
                .setParameter("lastName", lastName)
                .executeUpdate();
        }

        // No need to use
        // session.getTransaction().begin();
        // session.getTransaction().commit();
    }
}
    

The only downside is that it makes your code less portable when you are aiming for a Jakarta Persistence compatible application. And you might also have to keep up with Hibernate release API changes.

Wrapup

This post relates to Hibernate Stateless Session Test

Github code for this example: https://github.com/escay/demo-applications/tree/main/quarkus.hibernate.stateless.session

Things learned:

  • Running a unit / integration test from inside the IDE works very well with Quarkus.
  • There is no StatelessSession in Jakarta Persistence 3 / Jakarta EE 10 Core API available (yet). There is a git enhancement issue for this: https://github.com/jakartaee/persistence/issues/374
  • Using pure sql and the EntityManager as shown here could be sufficient for basic usage for now if you do not want to use Entity classes, but do want to use @Transactional business logic code.
  • You need at least one @Entity class to be able to use an EntityManager in Quarkus. See limitation: https://github.com/quarkusio/quarkus/issues/7148
  • Hibernate 6 setTupleTransformer is not available, but can be worked around using stream mapping. Old Jakarta Persistence issue https://github.com/jakartaee/persistence/issues/78 exists for a long time. I currently do not miss it because the mapping for “select from” sql queries is straightforward.
  • Hibernate makes a distinction between createNativeMutationQuery (inserts / updates) and createNativeQuery (selects), while the Jakarta Persistence EntityManager only provides ‘createNativeQuery’ for both inserts / updates and selects.