Quarkus - Simplified Hibernate ORM with Panache and Kotlin

Hibernate ORM is the de facto standard JPA implementation and is well-known in the Java ecosystem. Panache offers a new layer atop this familiar framework. This guide will not dive in to the specifics of either as those are already covered in the Panache guide. In this guide, we’ll cover the Kotlin specific changes needed to use Panache in your Kotlin-based Quarkus applications.

First: an example

As we saw in the Panache guide, Panache allows us to extend the functionality in our entities and repositories (also known as DAOs) with some automatically provided functionality. When using Kotlin, the approach is very similar to what we see in the Java version with a slight change or two. To Panache-enable your entity, you would define it something like:

  1. @Entity
  2. class Person: PanacheEntity {
  3. lateinit var name: String
  4. lateinit var birth: LocalDate
  5. lateinit var status: Status
  6. }

As you can see our entities remain simple. There is, however, a slight difference from the Java version. The Kotlin language doesn’t support the notion of static methods in quite the same way as Java does. Instead, we must use a [companion object](https://kotlinlang.org/docs/tutorials/kotlin-for-py/objects-and-companion-objects.html#companion-objects):

  1. @Entity
  2. class Person : PanacheEntity {
  3. companion object: PanacheCompanion<Person> { (1)
  4. fun findByName(name: String) = find("name", name).firstResult()
  5. fun findAlive() = list("status", Status.Alive)
  6. fun deleteStefs() = delete("name", "Stef")
  7. }
  8. lateinit var name: String (2)
  9. lateinit var birth: LocalDate
  10. lateinit var status: Status
  11. }
1The companion object holds all the methods not related to a specific instance allowing for general management and querying bound to a specific type.
2Here there are options, but we’ve chosen the lateinit approach. This allows us to declare these fields as non-null knowing they will be properly assigned either by the constructor (not shown) or by hibernate loading data from the database.
These types differ from the Java types mentioned in those tutorials. For Kotlin support, all the Panache types will be found in the io.quarkus.hibernate.orm.panache.kotlin package. This subpackage allows for the distinction between the Java and Kotlin variants and allows for both to be used unambiguously in a single project.

In the Kotlin version, we’ve simply moved the bulk of the active record pattern functionality to the companion object. Apart from this slight change, we can then work with our types in ways that map easily from the Java side of world.

Using the repository pattern

Defining your entity

When using the repository pattern, you can define your entities as regular JPA entities.

  1. @Entity
  2. class Person {
  3. @Id
  4. @GeneratedValue
  5. var id: Long? = null;
  6. lateinit var name: String
  7. lateinit var birth: LocalDate
  8. lateinit var status: Status
  9. }

Defining your repository

When using Repositories, you get the exact same convenient methods as with the active record pattern, injected in your Repository, by making them implement PanacheRepository:

  1. class PersonRepository: PanacheRepository<Person> {
  2. fun findByName(name: String) = find("name", name).firstResult()
  3. fun findAlive() = list("status", Status.Alive)
  4. fun deleteStefs() = delete("name", "Stef")
  5. }

All the operations that are defined on PanacheEntityBase are available on your repository, so using it is exactly the same as using the active record pattern, except you need to inject it:

  1. @Inject
  2. lateinit var personRepository: PersonRepository
  3. @GET
  4. fun count() = personRepository.count()

Most useful operations

Once you have written your repository, here are the most common operations you will be able to perform:

  1. // creating a person
  2. var person = Person()
  3. person.name = "Stef"
  4. person.birth = LocalDate.of(1910, Month.FEBRUARY, 1)
  5. person.status = Status.Alive
  6. // persist it
  7. personRepository.persist(person)
  8. // note that once persisted, you don't need to explicitly save your entity: all
  9. // modifications are automatically persisted on transaction commit.
  10. // check if it's persistent
  11. if(personRepository.isPersistent(person)){
  12. // delete it
  13. personRepository.delete(person)
  14. }
  15. // getting a list of all Person entities
  16. val allPersons = personRepository.listAll()
  17. // finding a specific person by ID
  18. person = personRepository.findById(personId) ?: throw Exception("No person with that ID")
  19. // finding all living persons
  20. val livingPersons = personRepository.list("status", Status.Alive)
  21. // counting all persons
  22. val countAll = personRepository.count()
  23. // counting all living persons
  24. val countAlive = personRepository.count("status", Status.Alive)
  25. // delete all living persons
  26. personRepository.delete("status", Status.Alive)
  27. // delete all persons
  28. personRepository.deleteAll()
  29. // delete by id
  30. val deleted = personRepository.deleteById(personId)
  31. // set the name of all living persons to 'Mortal'
  32. personRepository.update("name = 'Mortal' where status = ?1", Status.Alive)

All list methods have equivalent stream versions.

  1. val persons = personRepository.streamAll();
  2. val namesButEmmanuels = persons
  3. .map { it.name.toLowerCase() }
  4. .filter { it != "emmanuel" }
The stream methods require a transaction to work.
The rest of the documentation show usages based on the active record pattern only, but keep in mind that they can be performed with the repository pattern as well. The repository pattern examples have been omitted for brevity.

For more examples, please consult the Java version for complete details. Both APIs are the same and work identically except for some Kotlin-specific tweaks to make things feel more natural to Kotlin developers. These tweaks include things like better use of nullability and the lack of Optional on API methods.

Setting up and configuring Hibernate ORM with Panache

To get started using Panache with Kotlin, you can, generally, follow the steps laid out in the Java tutorial. The biggest change to configuring your project is the Quarkus artifact to include. You can, of course, keep the Java version if you need but if all you need are the Kotlin APIs then include the following dependency instead:

  1. <dependencies>
  2. <!-- Hibernate ORM specific dependencies -->
  3. <dependency>
  4. <groupId>io.quarkus</groupId>
  5. <artifactId>quarkus-hibernate-orm-panache-kotlin</artifactId> (1)
  6. </dependency>
  7. </dependencies>
1Note the addition of -kotlin on the end. Generally you’ll only need this version but if your project will be using both Java and Kotlin code, you can safely include both artifacts.