Edit this Page

Simplified Hibernate with Panache Next

experimental

Hibernate with Panache Next is the perfect solution if you want to get started using Hibernate in Quarkus.

This technology is considered experimental.

In experimental mode, early feedback is requested to mature the idea. There is no guarantee of stability nor long term presence in the platform until the solution matures. Feedback is welcome on our mailing list or as issues in our GitHub issue tracker.

For a full list of possible statuses, check our FAQ entry.

This extension is currently experimental, so its API may change, in particular names of classes or packages may change, even the module name. We are in the process of making it a public preview in order to obtain public feedback, so please report feedback either on Zulip or via GitHub issues.

Walk-through

Let’s take a progressive approach to learning how to use Hibernate with Panache, and start with a simple entity.

This guide is not going to go into the specifics of using either Hibernate, or its underlying Jakarta Persistence specification, because both already have great documentation that you can and should use for more in-depth knowledge.

Importing the extension, and configuration

pom.xml
<!-- Import the Hibernate with Panache extension -->
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-hibernate-panache-next</artifactId>
</dependency>
<!-- Pick your database driver -->
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-jdbc-postgresql</artifactId>
</dependency>
build.gradle
// Import the Hibernate with Panache extension
implementation("io.quarkus:quarkus-hibernate-panache-next")

// Pick your database driver
implementation("io.quarkus:quarkus-jdbc-postgresql")

You also have to configure the Hibernate processor, which is required:

pom.xml
<plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <!-- This setting is required for the annotation processor dependencies to be managed by Quarkus.
             More information is available in Maven compiler plugin documentation:
             https://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html#annotationProcessorPathsUseDepMgmt -->
        <annotationProcessorPathsUseDepMgmt>true</annotationProcessorPathsUseDepMgmt>
        <annotationProcessorPaths>
            <path>
                <groupId>org.hibernate.orm</groupId>
                <artifactId>hibernate-processor</artifactId>
                <!-- Note, no artifact version is required, it's managed by Quarkus.  -->
            </path>
            <!-- other processors that may be required by your app -->
        </annotationProcessorPaths>
        <!-- Other compiler plugin configuration options -->
    </configuration>
</plugin>
build.gradle
// Enforce the version management of your annotation processor dependencies,
// so that there's no need to define an explicit version of the hibernate-processor
annotationProcessor enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}")
annotationProcessor 'org.hibernate.orm:hibernate-processor'
You can use any of the supported JDBC drivers.

Make sure you configure your datasource in your application.properties (although you don’t have to in DEV mode: if you don’t, a dev service will be provided for you):

%prod.quarkus.datasource.username = hibernate
%prod.quarkus.datasource.password = hibernate
%prod.quarkus.datasource.jdbc.url = jdbc:postgresql://localhost:5432/hibernate_db

Our first entity

What this guide will focus on is the basics of how to use Hibernate with Panache, so let’s start with how to create an entity:

import io.quarkus.hibernate.panache.PanacheEntity;
import jakarta.persistence.Entity;

import java.time.LocalDate;

@Entity
public class Cat extends PanacheEntity {
    public String name;
    public LocalDate birth;
    public Breed breed;

    public enum Breed {
        CUTE, HAIRLESS;
    }
}

In order to map a Java class to a Database table, just:

  • Write a class

  • Annotate it with @Entity

  • Make it extend PanacheEntity

  • Make its fields public

And that’s it, now you can start creating instances of your entity, persist it to your database, after which point all changes to its fields will be automatically sent to the database (no need for any explicit update instruction), and even remove it from the database:

import jakarta.transaction.Transactional;

import java.time.LocalDate;
import java.util.List;

public class Code {

    @Transactional (1)
    public void method() {
        Cat cat = new Cat();
        cat.name = "Lucky";
        cat.birth = LocalDate.of(2015, 01, 12);
        cat.breed = Cat.Breed.CUTE;

        // Persist the cat
        cat.persist();

        // Make a change, no need to update it
        cat.name = "Luckynou";

        // Delete our cat
        cat.delete();
    }
}
1 This is required on any method which interacts with the database, in order to run the operations in a transaction.

Our first repository

Now that we know how to create, update and delete our entities, let’s see how we can query our database to find them, or even run delete queries.

Because query operations do not belong on instances of our entities, we will place these operations on another type called a Repository, and because those queries are intimately tied with the entities they operate on, we recommend placing them on an interface nested in the entity:

import io.quarkus.hibernate.panache.PanacheEntity;
import io.quarkus.hibernate.panache.PanacheRepository;
import jakarta.persistence.Entity;
import org.hibernate.annotations.processing.Find;
import org.hibernate.annotations.processing.HQL;

import java.time.LocalDate;
import java.util.List;

@Entity
public class Cat extends PanacheEntity {
    public String name;
    public LocalDate birth;
    public Breed breed;

    public enum Breed {
        CUTE, HAIRLESS;
    }

    public interface Repo extends PanacheRepository<Cat> {
        @Find
        Cat findByName(String name);

        @HQL("where breed = CUTE")
        List<Cat> findCute();

        @HQL("delete from Cat where name = :name")
        long deleteByName(String name);

        @HQL("delete from Cat where breed = HAIRLESS")
        long deleteHairless();
    }
}

A Hibernate with Panache repository is:

  • An interface nested in the entity (though it could be a toplevel interface if you prefer)

  • Which extends the PanacheRepository interface, with the entity in question as type parameter

  • And contains query methods, annotated with either @Find or @HQL or even @SQL (for native queries), or default methods with implementation of queries

You can use @Find methods to find single instances, or collections of entities, by using the parameters of the method to build a query. The Hibernate documentation has all the information you need about this.

A few common examples
@Find
public List<Cat> findAllCats(); (1)

@Find
public long countAllCats(); (2)

@Find
public Cat findCatByNameAndBreed(String name, Breed breed); (3)
1 Finder methods may return collections of entities
2 Or generate COUNT queries
3 Or single entities, and any number of parameters which must all match the queried entities.

Alternately, you can use @HQL or @SQL queries, which support HQL and SQL queries. Once again the Hibernate documentation has all the information you need.

A few common examples
@HQL("from Cat")
public List<Cat> findAllCats(); (1)

@HQL("select min(birth) from Cat")
public LocalDate oldestCat(); (2)

@HQL("where name = :name and breed = :breed")
public Cat findCatByNameAndBreed(String name, Breed breed); (3)
1 Query methods may return collections of entities
2 Or projections of a single column, even multiple columns with Object[] and List<Object[]> types
3 Queries can of course refer to method parameters.

The beauty of generated finder and query methods is that they are type-checked at build time: they will be validated and guarantee that you did not make any typo in the entity names, their fields, the HQL/SQL syntax, or the name of their parameters.

Using the repository

Using the repository can be done by simply injecting it where you intend to use it:

import jakarta.inject.Inject;
import jakarta.transaction.Transactional;

import java.time.LocalDate;
import java.util.List;

public class Code {

    @Inject
    Cat.Repo repo;

    @Transactional
    public void method() {
        Cat cat = new Cat();
        cat.name = "Lucky";
        cat.birth = LocalDate.of(2015, 01, 12);
        cat.breed = Cat.Breed.CUTE;

        // Persist the cat
        cat.persist();

        // Make a change, no need to update it
        cat.name = "Luckynou";

        // Find our cat
        cat = repo.findByName("Luckynou");

        // Find cute cats
        List<Cat> cuteCats = repo.findCute();

        // Delete our cat
        cat.delete();

        // Delete queries
        repo.deleteByName("Lucky");
        repo.deleteHairless();
    }
}

Now, as an added benefit, you can also access the repository using a handy shortcut generated static method on the generated Metamodel of your entity, which is super useful for discovery of operations (just type Cat_.repo(). and see all your methods) and to avoid making a detour outside of your method to add an injected field, such as in this startup code that will delete all your cats (don’t do this in production!!):

import io.quarkus.runtime.Startup;
import jakarta.transaction.Transactional;

public class OnStart {
    @Startup
    @Transactional
    public void startupMethod() {
        Cat_.repo().deleteAll();
    }
}

All the repositories you define as nested interfaces of your entity will be available under their generated metamodel class under accessor methods of the same name as the repository. This is strictly equivalent to injecting the repository type, and in fact uses CDI under the hood to look up the repository.

The PanacheRepository super type

Just like in previous versions of Hibernate ORM and Hibernate Reactive with Panache, the PanacheRepository type comes packed with most of the operations you need to work on your entity, such as, out of the box:

import io.quarkus.hibernate.panache.blocking.PanacheBlockingQuery;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import org.hibernate.Session;

import java.time.LocalDate;
import java.util.List;
import java.util.Optional;

public class Code {

    @Inject
    Cat.Repo repo;

    @Transactional
    public void repositoryOperations(Cat cat) {
        // entity operations
        repo.persist(cat);
        repo.delete(cat);
        boolean isPersistent = repo.isPersistent(cat);
        // operations on all entities
        long count = repo.count();
        long deleted = repo.deleteAll();
        List<Cat> allCats = repo.listAll();
        repo.streamAll().forEach(kitty -> kitty.name = kitty.name.toUpperCase());
        PanacheBlockingQuery<Cat> catQuery = repo.findAll();
        // operations on the Hibernate session
        repo.flush();
        Session session = repo.getSession();
        // ID-related operations
        boolean wasDeleted = repo.deleteById(cat.id);
        Cat foundCat = repo.findById(cat.id);
        Optional<Cat> optionalCat = repo.findByIdOptional(cat.id);
    }
}

Non-type-safe queries

With generated finder and query methods, as we’ve previously shown, everything is validated at build-time, but if you want to write non-type-safe queries, you can always use the provided methods of PanacheRepository:

import io.quarkus.hibernate.panache.blocking.PanacheBlockingQuery;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import org.hibernate.Session;

import java.time.LocalDate;
import java.util.List;
import java.util.Optional;

public class Code {

    @Inject
    Cat.Repo repo;

    @Transactional
    public void nonTypeSafeQueries() {
        // Find a cat by name
        Cat cat = repo.find(Cat_.NAME, "Lucky").singleResult();
        // All cute cats
        List<Cat> cuteCats = repo.list(Cat_.BREED, Cat.Breed.CUTE);
        // Cats with no known birth date
        repo.stream(Cat_.BIRTH+" is null").forEach(kitty -> System.err.println(kitty));
        // Get rid of non-cute cats
        long deleted = repo.delete(Cat_.BREED, Cat.Breed.HAIRLESS);
        // Count ugly cats with no name
        repo.count("breed = ?1 and name is null", Cat.Breed.HAIRLESS);
        // Make every cat cute
        repo.update("breed = CUTE");
    }
}

About entity identifiers

In the example above, we extended the PanacheEntity type, and did not define any database identifier, that’s because PanacheEntity comes with a default generated database identifier, so you don’t have to worry about it. It does this by extending the WithId.AutoLong class, which provides a generated database identifier of type Long.

You can choose to extend the WithId.AutoString for a String identifier, or WithId.AutoUUID for a UUID identifier, or even extend WithId<IdType> to automatically get an attribute of the form:

@Id
@GeneratedValue
public IdType id;

Naturally, you can also provide your own database identifier explicitly and implement the PanacheEntity.Managed interface in your entity, as well as use the PanacheRepository.Managed interface for your repository, in order to specify you database identifier type:

import io.quarkus.hibernate.panache.PanacheEntity;
import io.quarkus.hibernate.panache.PanacheRepository;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import org.hibernate.annotations.processing.Find;
import org.hibernate.annotations.processing.HQL;

import java.time.LocalDate;
import java.util.List;

@Entity
public class CatWithId implements PanacheEntity.Managed {
    @Id
    public String id;
    public String name;
    public LocalDate birth;
    public Breed breed;

    public enum Breed {
        CUTE, HAIRLESS;
    }

    public interface Repo extends PanacheRepository.Managed<CatWithId, String> {
        @Find
        CatWithId findByName(String name);

        @HQL("where breed = CUTE")
        List<CatWithId> findCute();

        @HQL("delete from Cat where name = :name")
        long deleteByName(String name);

        @HQL("delete from Cat where breed = HAIRLESS")
        long deleteHairless();
    }
}

Here is a list of entity supertypes you can use and the ID type they provide, but keep in mind you do not have to extend any of these types if you define you own ID:

ID type Supertype Shortcut type

Long

WithId.AutoLong

PanacheEntity

UUID

WithId.AutoUUID

String

WithId.String

T

WithId<Id>

Use stateless sessions

Out of the box, your subtype of PanacheEntity will be managed by Hibernate ORM, and every change to the entity will be automatically sent to the database without requiring any explicit update operation.

If on the other hand, you wish to make every update operation explicit, then you need to use what Hibernate ORM calls a stateless session.

In this case, you need to extend WithId.AutoLong (or any other ID class or provide your own ID), and implement the PanacheEntity.Stateless and PanacheRepository.Stateless interfaces:

import io.quarkus.hibernate.panache.PanacheEntity;
import io.quarkus.hibernate.panache.PanacheRepository;
import io.quarkus.hibernate.panache.WithId;
import jakarta.persistence.Entity;
import org.hibernate.annotations.processing.Find;
import org.hibernate.annotations.processing.HQL;

import java.time.LocalDate;
import java.util.List;

@Entity
public class Cat extends WithId.AutoLong implements PanacheEntity.Stateless {
    public String name;
    public LocalDate birth;
    public Breed breed;

    public enum Breed {
        CUTE, HAIRLESS;
    }

    public interface Repo extends PanacheRepository.Stateless<Cat, Long> {
        @Find
        Cat findByName(String name);

        @HQL("where breed = CUTE")
        List<Cat> findCute();

        @HQL("delete from Cat where name = :name")
        long deleteByName(String name);

        @HQL("delete from Cat where breed = HAIRLESS")
        long deleteHairless();
    }
}

As you can see, your entity definition is exactly the same, besides the two interfaces. But now, your entities will not be managed, so every update operation has to be explicit:

import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import org.hibernate.StatelessSession;

import java.time.LocalDate;
import java.util.List;

public class Code {

    @Inject
    Cat.Repo repo;

    @Transactional
    public void method() {
        Cat cat = new Cat();
        cat.name = "Lucky";
        cat.birth = LocalDate.of(2015, 01, 12);
        cat.breed = Cat.Breed.CUTE;

        // Persist the cat
        cat.insert();

        // Make a change, we need to update it
        cat.name = "Luckynou";
        cat.update();

        // Find our cat
        cat = repo.findByName("Luckynou");

        // Find cute cats
        List<Cat> cuteCats = repo.findCute();

        // Delete our cat
        cat.delete();

        // Delete queries
        repo.deleteByName("Lucky");
        repo.deleteHairless();
    }
}

As you can see, the only differences with a managed entity are:

  • Any change to the entity instance fields must be explicitly pushed to the database by calling update() either on the entity or its repository

  • You need to call insert() instead of persist() to insert the entity in the database

  • Your session will be of type StatelessSession instead of Session.

But that’s mostly it. Everything else stays the same, in particular for queries and how to obtain entities.

If you’re using stateless sessions, you can also use the Jakarta Data type of repositories.

Let’s get reactive

You want to start using your entities in a reactive application? Let’s start by importing Hibernate Reactive in your pom.xml as well as a data source for your database:

pom.xml
<!-- Enable Hibernate Reactive support -->
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-hibernate-reactive</artifactId>
</dependency>
<!-- Pick your database reactive driver -->
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-reactive-pg-client</artifactId>
</dependency>
<!-- FIXME: this will not be required in the future -->
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-hibernate-reactive-panache-common</artifactId>
</dependency>
build.gradle
// Enable Hibernate Reactive support
implementation("io.quarkus:quarkus-hibernate-reactive")
// Pick your database reactive driver
implementation("io.quarkus:quarkus-reactive-pg-client")
// FIXME: this will not be required in the future
implementation("io.quarkus:quarkus-hibernate-reactive-panache-common")
You can use any of the supported reactive drivers.

Make sure you configure your reactive datasource in your application.properties (although you don’t have to in DEV mode: if you don’t, a dev service will be provided for you):

quarkus.datasource.username = quarkus_test
quarkus.datasource.password = quarkus_test
quarkus.datasource.reactive.url = vertx-reactive:postgresql://localhost/quarkus_test (1)

Now, in your code, the only differences with a regular managed session entity are:

  • Your entity implements PanacheEntity.Reactive

  • Your repository extends PanacheRepository.Reactive

  • All the operations return a Uni<T> instead of T, which is the standard Mutiny reactive type

import io.quarkus.hibernate.panache.PanacheEntity;
import io.quarkus.hibernate.panache.PanacheRepository;
import io.quarkus.hibernate.panache.WithId;
import io.smallrye.mutiny.Uni;
import jakarta.persistence.Entity;
import org.hibernate.annotations.processing.Find;
import org.hibernate.annotations.processing.HQL;

import java.time.LocalDate;
import java.util.List;

@Entity
public class Cat extends WithId.AutoLong implements PanacheEntity.Reactive {
    public String name;
    public LocalDate birth;
    public Breed breed;

    public enum Breed {
        CUTE, HAIRLESS;
    }

    public interface Repo extends PanacheRepository.Reactive<Cat, Long> {
        @Find
        Uni<Cat> findByName(String name);

        @HQL("where breed = CUTE")
        Uni<List<Cat>> findCute();

        @HQL("delete from Cat where name = :name")
        Uni<Integer> deleteByName(String name);

        @HQL("delete from Cat where breed = HAIRLESS")
        Uni<Integer> deleteHairless();
    }
}

And now when you want to use your entity or its repository, besides using @WithTransaction in place of @Transactional, you have to compose operations as you normally do in reactive code with Mutiny, but the operations are exactly the same otherwise:

import io.quarkus.hibernate.reactive.panache.common.WithTransaction;
import io.smallrye.mutiny.Uni;
import jakarta.inject.Inject;

import java.time.LocalDate;
import java.util.List;
import java.util.Optional;

public class Code {

    @Inject
    Cat.Repo repo;

    @WithTransaction
    public Uni<Void> method() {
        Cat cat = new Cat();
        cat.name = "Lucky";
        cat.birth = LocalDate.of(2015, 01, 12);
        cat.breed = Cat.Breed.CUTE;

        // Persist the cat
        return cat.persist()
                // Make a change, no need to update it
                .invoke(() -> cat.name = "Luckynou")
                // Find our cat
                .chain(() -> repo.findByName("Luckynou"))
                // Find cute cats
                .chain((Cat foundCat) -> repo.findCute())
                // Delete our cat
                .chain((List<Cat> cuteCats) -> cat.delete())
                // Delete queries
                .chain(v -> repo.deleteByName("Lucky"))
                .chain((Integer deletedCount) -> repo.deleteHairless())
                .replaceWithVoid();
    }
}

Reactive and stateless

If you want to manage manually your entity changes, then you can use a stateless session by switching to the PanacheEntity.Reactive.Stateless for your entity, and PanacheRepository.Reactive.Stateless:

import io.quarkus.hibernate.panache.PanacheEntity;
import io.quarkus.hibernate.panache.PanacheRepository;
import io.quarkus.hibernate.panache.WithId;
import io.smallrye.mutiny.Uni;
import jakarta.persistence.Entity;
import org.hibernate.annotations.processing.Find;
import org.hibernate.annotations.processing.HQL;

import java.time.LocalDate;
import java.util.List;

@Entity
public class Cat extends WithId.AutoLong implements PanacheEntity.Reactive.Stateless {
    public String name;
    public LocalDate birth;
    public Breed breed;

    public enum Breed {
        CUTE, HAIRLESS;
    }

    public interface Repo extends PanacheRepository.Reactive.Stateless<Cat, Long> {
        @Find
        Uni<Cat> findByName(String name);

        @HQL("where breed = CUTE")
        Uni<List<Cat>> findCute();

        @HQL("delete from Cat where name = :name")
        Uni<Integer> deleteByName(String name);

        @HQL("delete from Cat where breed = HAIRLESS")
        Uni<Integer> deleteHairless();
    }
}

And for your usage code, the only change is the standard use of manually calling update(), and using insert() instead of persist():

import io.quarkus.hibernate.reactive.panache.common.WithTransaction;
import io.smallrye.mutiny.Uni;
import jakarta.inject.Inject;

import java.time.LocalDate;
import java.util.List;

public class Code {

    @Inject
    Cat.Repo repo;

    @WithTransaction
    public Uni<Void> method() {
        Cat cat = new Cat();
        cat.name = "Lucky";
        cat.birth = LocalDate.of(2015, 01, 12);
        cat.breed = Cat.Breed.CUTE;

        // Persist the cat
        return cat.insert()
                // Make a change, we need to update it
                .chain(() -> {
                    cat.name = "Luckynou";
                    return cat.update();
                })
                // Find our cat
                .chain(() -> repo.findByName("Luckynou"))
                // Find cute cats
                .chain((Cat foundCat) -> repo.findCute())
                // Delete our cat
                .chain((List<Cat> cuteCats) -> cat.delete())
                // Delete queries
                .chain(v -> repo.deleteByName("Lucky"))
                .chain((Integer deletedCount) -> repo.deleteHairless())
                .replaceWithVoid();
    }
}
If you’re using stateless sessions, you can also use the Jakarta Data type of repositories.

Combining blocking, reactive, managed, stateless code

Let’s say you want to use an entity in a blocking managed session code, but also in a reactive stateless code: this is possible no matter which supertype of your entity you picked. You should pick the supertype that represents your majority of use-cases, but any one you pick, you can always obtain the alternative operations by using the .statelessReactive() method on the entity:

import io.quarkus.hibernate.reactive.panache.common.WithTransaction;
import io.smallrye.mutiny.Uni;
import jakarta.transaction.Transactional;

import java.time.LocalDate;

public class Code {

    @Transactional
    public void blockingManagedMethod() {
        Cat cat = new Cat();
        cat.name = "Lucky";
        cat.birth = LocalDate.of(2015, 01, 12);
        cat.breed = Cat.Breed.CUTE;

        // Persist the cat
        cat.persist();

        // Make a change, no need to update it
        cat.name = "Luckynou";
    }

    @WithTransaction
    public Uni<Void> reactiveStatelessMethod(Long catId) {
        Cat cat = new Cat();
        cat.name = "Lucky";
        cat.birth = LocalDate.of(2015, 01, 12);
        cat.breed = Cat.Breed.CUTE;

        // Insert the cat
        return cat.statelessReactive().insert()
                .chain(() -> {
                    // Make a change, we need to update it
                    cat.name = "Luckynou";
                    return cat.statelessReactive().update();
                });

    }
}

All the alternative entity operations are available from these methods:

Session type Entity accessor Equivalent entity type

Session

.managedBlocking()

PanacheEntity.Managed

StatelessSession

.statelessBlocking()

PanacheEntity.Stateless

Mutiny.Session

.managedReactive()

PanacheEntity.Reactive

Mutiny.StatelessSession

.statelessReactive()

PanacheEntity.Reactive.Stateless

Similarly, for all the repository operations, you can obtain alternate repositories for your entity from the generated metamodel accessors, or by injecting them with @Inject:

Session type Metamodel accessor Repository type

Session

Cat_.managedBlocking()

PanacheRepository.Managed<Cat, Long>

StatelessSession

Cat_.statelessBlocking()

PanacheRepository.Stateless<Cat, Long>

Mutiny.Session

Cat_.managedReactive()

PanacheRepository.Reactive<Cat, Long>

Mutiny.StatelessSession

Cat_.statelessReactive()

PanacheRepository.Reactive.Stateless<Cat, Long>

But you can also define any number of repositories for your custom operations within your entity:

import io.quarkus.hibernate.panache.PanacheEntity;
import io.quarkus.hibernate.panache.PanacheRepository;
import io.smallrye.mutiny.Uni;
import jakarta.persistence.Entity;
import org.hibernate.annotations.processing.Find;
import org.hibernate.annotations.processing.HQL;

import java.time.LocalDate;
import java.util.List;

@Entity
public class Cat extends PanacheEntity {
    public String name;
    public LocalDate birth;
    public Breed breed;

    public enum Breed {
        CUTE, HAIRLESS;
    }

    public interface Repo extends PanacheRepository<Cat> {
        @Find
        Cat findByName(String name);
    }

    public interface TheOtherRepo extends PanacheRepository.Reactive.Stateless<Cat, Long> {
        @Find
        Uni<Cat> findByName(String name);
    }
}

And then you can inject both types of repositories:

import io.quarkus.hibernate.reactive.panache.common.WithTransaction;
import io.smallrye.mutiny.Uni;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;

public class Code {

    @Inject
    Cat.Repo repo;

    @Inject
    Cat.TheOtherRepo theOtherRepo;

    @Transactional
    public void blockingManagedMethod() {
        Cat cat = repo.findByName("Lucky");
    }

    @WithTransaction
    public Uni<Void> reactiveStatelessMethod() {
        return theOtherRepo.findByName("Lucky")
                .invoke(cat -> System.err.println(cat))
                .replaceWithVoid();
    }
}
you can of course also use the generated metamodel accessors instead of injecting these repositories, with Cat_.repo() and Cat_.theOtherRepo().

Recap

Here is a table listing all the options you have for your entity and repository supertypes, depending on which model of operation you prefer, and which ID type you want. Remember you don’t have to extend WithId<Id> if you define your own ID entity field:

Session type ID type Entity superclass Entity superinterface Repository supertype

Session (managed, blocking)

Long

PanacheEntity

PanacheRepository<Entity>

Session (managed, blocking)

Id

WithId<Id>

PanacheEntity.Managed

PanacheRepository.Managed<Entity, Id>

StatelessSession (stateless, blocking)

Id

WithId<Id>

PanacheEntity.Stateless

PanacheRepository.Stateless<Entity, Id>

Mutiny.Session (managed, reactive)

Id

WithId<Id>

PanacheEntity.Reactive

PanacheRepository.Reactive<Entity, Id>

Mutiny.StatelessSession (stateless, reactive)

Id

WithId<Id>

PanacheEntity.Reactive.Stateless

PanacheRepository.Reactive.Stateless<Entity, Id>

Startup code

For blocking operations, invoking startup code is pretty trivial:

import io.quarkus.runtime.Startup;
import jakarta.transaction.Transactional;

public class OnStart {
    @Startup
    @Transactional
    public void startupMethod() {
        Cat_.repo().deleteAll();
    }
}

For reactive operations, it is a little more complex at the moment, because you need a regular @Startup method, from which you invoke your reactive @WithTransaction method from within a call to VertxContextSupport.subscribeAndAwait (don’t worry, we are working on making this easier):

import io.quarkus.hibernate.reactive.panache.common.WithTransaction;
import io.quarkus.runtime.Startup;
import io.quarkus.vertx.VertxContextSupport;
import io.smallrye.mutiny.Uni;

public class OnStart {
    @Startup
    public void start() throws Throwable {
        VertxContextSupport.subscribeAndAwait(() -> runit());
    }

    @WithTransaction
    Uni<Void> runit(){
        return Cat_.repo().deleteAll().replaceWithVoid();
    }
}

Jakarta Data

WARN: Jakarta Data only works with stateless sessions, though it does support blocking and reactive variants.

Naturally, you can also use Jakarta Data to define your repositories. For this you need to import the module:

pom.xml
<dependency>
    <groupId>jakarta.data</groupId>
    <artifactId>jakarta.data-api</artifactId>
</dependency>
build.gradle
implementation 'jakarta.data:jakarta.data-api'

And then you can write your entity as usual, but for repositories you extend CrudRepository (or nothing if you wish to implement all the methods yourself), using the entity and its ID types as type parameters, and you must annotate the repository with @Repository.

Otherwise, just replace @HQL with @Query and change the package import of @Find, and you can even write @Delete finder methods:

import jakarta.data.repository.CrudRepository;
import jakarta.data.repository.Delete;
import jakarta.data.repository.Find;
import jakarta.data.repository.Query;
import jakarta.data.repository.Repository;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;

import java.time.LocalDate;
import java.util.List;

@Entity
public class Cat {
    @Id
    @GeneratedValue
    public Long id;
    public String name;
    public LocalDate birth;
    public Breed breed;

    public enum Breed {
        CUTE, HAIRLESS;
    }

    @Repository
    public interface Repo extends CrudRepository<Cat, Long> {
        @Find (1)
        Cat findByName(String name);

        @Query("where breed = CUTE") (2)
        List<Cat> findCute();

        @Delete (3)
        long deleteByName(String name);

        @Query("delete from Cat where breed = HAIRLESS") (4)
        long deleteHairless();
    }
}
1 This is a regular finder method
2 Use @Query in place of @HQL, they’re the same everything
3 You can write delete finder methods. Just like finder methods, except their action is to delete entities, optionally returning how many were deleted.
4 You can always explicitly write delete methods as query methods.

But otherwise you can still make your entity extend PanacheEntity or any of its variants, and you still get your repository injectable, or via the Cat_.repo() accessor.

Please refer to the corresponding Hibernate Data Repositories and Jakarta Data guides to learn what else they have to offer.

You can also use the reactive variants by returning Uni types from your repository methods, or add other types of repositories in your entity to support managed entities.

HQL, JD-QL, Jakarta-QL, Panache-QL

There are different query languages used in this API:

  • The Hibernate Query Language (HQL), which is the query language used by Hibernate, supported by the @HQL annotation.

  • The Jakarta Persistence Query Language (JP-QL), which is a subset of the Hibernate Query Language. It is currently being moved to its own spec called Jakarta Query.

  • The Jakarta Data Query Language (JD-QL), which is a subset of the Jakarta Persistence Query Language, supported by the @Query annotation.

  • The Panache Query Language, which is a superset of the Hibernate Query Language adding very few shortcuts, which is supported in all the PanacheRepository queries.

Panache Query Language

Normally, most HQL queries are of this form: from EntityName [where …​] [order by …​], with optional elements at the end.

If your select query does not start with from, select or with, we support the following additional forms:

  • order by …​ which will expand to from EntityName order by …​

  • <singleAttribute> (and single parameter) which will expand to from EntityName where <singleAttribute> = ?

  • where <query> will expand to from EntityName where <query>

  • <query> will expand to from EntityName where <query>

If your update query does not start with update, we support the following additional forms:

  • from EntityName …​ which will expand to update EntityName …​

  • set? <singleAttribute> (and single parameter) which will expand to update EntityName set <singleAttribute> = ?

  • set? <update-query> will expand to update EntityName set <update-query>

If your delete query does not start with delete, we support the following additional forms:

  • from EntityName …​ which will expand to delete from EntityName …​

  • <singleAttribute> (and single parameter) which will expand to delete from EntityName where <singleAttribute> = ?

  • <query> will expand to delete from EntityName where <query>

Related content