Quarkus - Simplified Hibernate ORM with Panache

    What we’re doing in Panache is allow you to write your Hibernate ORM entities like this:

    You have noticed how much more compact and readable the code is? Does this look interesting? Read on!

    what was described above is essentially the active record pattern, sometimes just called the entity pattern. Hibernate with Panache also allows for the use of the more classical via PanacheRepository.

    Solution

    We recommend that you follow the instructions in the next sections and create the application step by step. However, you can go right to the completed example.

    Clone the Git repository: git clone [https://github.com/quarkusio/quarkus-quickstarts.git](https://github.com/quarkusio/quarkus-quickstarts.git), or download an .

    The solution is located in the hibernate-orm-panache-quickstart directory.

    Setting up and configuring Hibernate ORM with Panache

    To get started:

    • add your settings in application.properties

    • annotate your entities with @Entity

    • make your entities extend PanacheEntity (optional if you are using the repository pattern)

    Follow the Hibernate set-up guide for all configuration.

    In your pom.xml, add the following dependencies:

    • the Panache JPA extension

    • your JDBC driver extension (quarkus-jdbc-postgresql, quarkus-jdbc-h2, quarkus-jdbc-mariadb, …​)

    1. <dependencies>
    2. <!-- Hibernate ORM specific dependencies -->
    3. <dependency>
    4. <groupId>io.quarkus</groupId>
    5. <artifactId>quarkus-hibernate-orm-panache</artifactId>
    6. </dependency>
    7. <!-- JDBC driver dependencies -->
    8. <dependency>
    9. <groupId>io.quarkus</groupId>
    10. <artifactId>quarkus-jdbc-postgresql</artifactId>
    11. </dependency>
    12. </dependencies>

    Then add the relevant configuration properties in application.properties.

    1. # configure your datasource
    2. quarkus.datasource.db-kind = postgresql
    3. quarkus.datasource.username = sarah
    4. quarkus.datasource.password = connor
    5. quarkus.datasource.jdbc.url = jdbc:postgresql://localhost:5432/mydatabase
    6. # drop and create the database at startup (use `update` to only update the schema)
    7. quarkus.hibernate-orm.database.generation = drop-and-create

    Solution 1: using the active record pattern

    To define a Panache entity, simply extend PanacheEntity, annotate it with @Entity and add your columns as public fields:

    1. @Entity
    2. public class Person extends PanacheEntity {
    3. public String name;
    4. public LocalDate birth;
    5. public Status status;
    6. }

    You can put all your JPA column annotations on the public fields. If you need a field to not be persisted, use the @Transient annotation on it. If you need to write accessors, you can:

    1. @Entity
    2. public class Person extends PanacheEntity {
    3. public String name;
    4. public LocalDate birth;
    5. public Status status;
    6. // return name as uppercase in the model
    7. public String getName(){
    8. return name.toUpperCase();
    9. }
    10. // store all names in lowercase in the DB
    11. public void setName(String name){
    12. this.name = name.toLowerCase();
    13. }
    14. }

    And thanks to our field access rewrite, when your users read person.name they will actually call your getName() accessor, and similarly for field writes and the setter. This allows for proper encapsulation at runtime as all fields calls will be replaced by the corresponding getter/setter calls.

    Most useful operations

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

    1. // creating a person
    2. Person person = new Person();
    3. person.name = "Stef";
    4. person.birth = LocalDate.of(1910, Month.FEBRUARY, 1);
    5. person.status = Status.Alive;
    6. // persist it
    7. person.persist();
    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(person.isPersistent()){
    12. // delete it
    13. person.delete();
    14. }
    15. // getting a list of all Person entities
    16. List<Person> allPersons = Person.listAll();
    17. // finding a specific person by ID
    18. person = Person.findById(personId);
    19. // finding a specific person by ID via an Optional
    20. Optional<Person> optional = Person.findByIdOptional(personId);
    21. person = optional.orElseThrow(() -> new NotFoundException());
    22. // finding all living persons
    23. List<Person> livingPersons = Person.list("status", Status.Alive);
    24. // counting all persons
    25. long countAll = Person.count();
    26. // counting all living persons
    27. long countAlive = Person.count("status", Status.Alive);
    28. // delete all living persons
    29. Person.delete("status", Status.Alive);
    30. // delete all persons
    31. Person.deleteAll();
    32. // delete by id
    33. boolean deleted = Person.deleteById(personId);
    34. // set the name of all living persons to 'Mortal'
    35. Person.update("name = 'Mortal' where status = ?1", Status.Alive);

    All list methods have equivalent stream versions.

    1. try (Stream<Person> persons = Person.streamAll()) {
    2. List<String> namesButEmmanuels = persons
    3. .map(p -> p.name.toLowerCase() )
    4. .filter( n -> ! "emmanuel".equals(n) )
    5. .collect(Collectors.toList());
    6. }
    The stream methods require a transaction to work.
    As they perform I/O operations, they should be closed via the close() method or via a try-with-resource to close the underlying ResultSet. If not, you will see warnings from Agroal that will close the underlying ResultSet for you.

    Adding entity methods

    Add custom queries on your entities inside the entities themselves. That way, you and your co-workers can find them easily, and queries are co-located with the object they operate on. Adding them as static methods in your entity class is the Panache Active Record way.

    1. @Entity
    2. public class Person extends PanacheEntity {
    3. public String name;
    4. public LocalDate birth;
    5. public Status status;
    6. public static Person findByName(String name){
    7. return find("name", name).firstResult();
    8. }
    9. public static List<Person> findAlive(){
    10. return list("status", Status.Alive);
    11. }
    12. public static void deleteStefs(){
    13. delete("name", "Stef");
    14. }
    15. }

    Defining your entity

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

    1. @Entity
    2. public class Person {
    3. @Id @GeneratedValue private Long id;
    4. private String name;
    5. private LocalDate birth;
    6. private Status status;
    7. public Long getId(){
    8. return id;
    9. }
    10. public void setId(Long id){
    11. this.id = id;
    12. }
    13. public String getName() {
    14. return name;
    15. }
    16. public void setName(String name) {
    17. this.name = name;
    18. }
    19. public LocalDate getBirth() {
    20. return birth;
    21. }
    22. public void setBirth(LocalDate birth) {
    23. this.birth = birth;
    24. }
    25. public Status getStatus() {
    26. return status;
    27. }
    28. public void setStatus(Status status) {
    29. this.status = status;
    30. }
    31. }
    If you don’t want to bother defining getters/setters for your entities, you can make them extend PanacheEntityBase and Quarkus will generate them for you. You can even extend PanacheEntity and take advantage of the default ID it provides.

    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 implements PanacheRepository:

    1. @ApplicationScoped
    2. public class PersonRepository implements PanacheRepository<Person> {
    3. // put your custom logic here as instance methods
    4. public Person findByName(String name){
    5. return find("name", name).firstResult();
    6. }
    7. public List<Person> findAlive(){
    8. return list("status", Status.Alive);
    9. }
    10. public void deleteStefs(){
    11. delete("name", "Stef");
    12. }
    13. }

    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. PersonRepository personRepository;
    3. @GET
    4. public long count(){
    5. return personRepository.count();
    6. }

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

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

    All list methods have equivalent stream versions.

    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.

    Advanced Query

    Paging

    You should only use list and stream methods if your table contains small enough data sets. For larger data sets you can use the find method equivalents, which return a PanacheQuery on which you can do paging:

    1. // create a query for all living persons
    2. PanacheQuery<Person> livingPersons = Person.find("status", Status.Alive);
    3. // make it use pages of 25 entries at a time
    4. livingPersons.page(Page.ofSize(25));
    5. // get the first page
    6. List<Person> firstPage = livingPersons.list();
    7. // get the second page
    8. List<Person> secondPage = livingPersons.nextPage().list();
    9. // get page 7
    10. List<Person> page7 = livingPersons.page(Page.of(7, 25)).list();
    11. // get the number of pages
    12. int numberOfPages = livingPersons.pageCount();
    13. // get the total number of entities returned by this query without paging
    14. long count = livingPersons.count();
    15. // and you can chain methods of course
    16. return Person.find("status", Status.Alive)
    17. .page(Page.ofSize(25))
    18. .nextPage()
    19. .stream()

    The PanacheQuery type has many other methods to deal with paging and returning streams.

    Using a range instead of pages

    PanacheQuery also allows range-based queries.

    1. // create a query for all living persons
    2. PanacheQuery<Person> livingPersons = Person.find("status", Status.Alive);
    3. // make it use a range: start at index 0 until index 24 (inclusive).
    4. livingPersons.range(0, 24);
    5. // get the range
    6. List<Person> firstRange = livingPersons.list();
    7. // to get the next range, you need to call range again
    8. List<Person> secondRange = livingPersons.range(25, 49).list();

    Sorting

    All methods accepting a query string also accept the following simplified query form:

    1. List<Person> persons = Person.list("order by name,birth");

    But these methods also accept an optional Sort parameter, which allows your to abstract your sorting:

    1. List<Person> persons = Person.list(Sort.by("name").and("birth"));
    2. // and with more restrictions
    3. List<Person> persons = Person.list("status", Sort.by("name").and("birth"), Status.Alive);

    The Sort class has plenty of methods for adding columns and specifying sort direction.

    Simplified queries

    Normally, 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, we support the following additional forms:

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

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

    • <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 from EntityName …​

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

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

    You can also write your queries in plain :
    1. Order.find("select distinct o from Order o left join fetch o.lineItems");
    2. Order.update("update from Person set name = 'Mortal' where status = ?", Status.Alive);

    You can reference a named query instead of a (simplified) HQL query by prefixing its name with the ‘#’ character.

    1. @Entity
    2. @NamedQuery(name = "Person.getByName", query = "from Person where name = :name")
    3. public class Person extends PanacheEntity {
    4. public String name;
    5. public LocalDate birth;
    6. public Status status;
    7. public static Person findByName(String name){
    8. return find("#Person.getByName", name).firstResult();
    9. }
    10. }

    Query parameters

    You can pass query parameters by index (1-based) as shown below:

    1. Person.find("name = ?1 and status = ?2", "stef", Status.Alive);

    Or by name using a Map:

    1. Map<String, Object> params = new HashMap<>();
    2. params.put("name", "stef");
    3. params.put("status", Status.Alive);
    4. Person.find("name = :name and status = :status", params);

    Or using the convenience class Parameters either as is or to build a Map:

    1. // generate a Map
    2. Person.find("name = :name and status = :status",
    3. Parameters.with("name", "stef").and("status", Status.Alive).map());
    4. // use it as-is
    5. Person.find("name = :name and status = :status",
    6. Parameters.with("name", "stef").and("status", Status.Alive));

    Every query operation accepts passing parameters by index (Object…​), or by name (Map<String,Object> or Parameters).

    Query projection

    Query projection can be done with the project(Class) method on the PanacheQuery object that is returned by the find() methods.

    You can use it to restrict which fields will be returned by the database.

    Hibernate will use DTO projection and generate a SELECT clause with the attributes from the projection class. This is also called dynamic instantiation or constructor expression, more info can be found on the Hibernate guide: hql select clause

    The projection class needs to be a valid Java Bean and have a constructor that contains all its attributes, this constructor will be used to instantiate the projection DTO instead of using the entity class. This must be the only constructor of the class.

    1. import io.quarkus.runtime.annotations.RegisterForReflection;
    2. @RegisterForReflection (1)
    3. public class PersonName {
    4. public final String name; (2)
    5. public PersonName(String name){ (3)
    6. this.name = name;
    7. }
    8. }
    9. // only 'name' will be loaded from the database
    10. PanacheQuery<PersonName> query = Person.find("status", Status.Alive).project(PersonName.class);
    1. If you plan to deploy your application as a native executable, you must register manually the projection class for reflection.

    2. We use public fields here, but you can use private fields and getters/setters if you prefer.

    3. This constructor will be used by Hibernate, it must be the only constructor in your class and have all the class attributes as parameters.

    The implementation of the project(Class) method uses the constructor’s parameter names to build the select clause of the query, so the compiler must be configured to store parameter names inside the compiled class. This is enabled by default if you are using the Quarkus Maven archetype. If you are not using it, add the property <maven.compiler.parameters>true</maven.compiler.parameters> to your pom.xml.

    Transactions

    Make sure to wrap methods modifying your database (e.g. entity.persist()) within a transaction. Marking a CDI bean method @Transactional will do that for you and make that method a transaction boundary. We recommend doing so at your application entry point boundaries like your REST endpoint controllers.

    JPA batches changes you make to your entities and sends changes (it’s called flush) at the end of the transaction or before a query. This is usually a good thing as it’s more efficient. But if you want to check optimistic locking failures, do object validation right away or generally want to get immediate feedback, you can force the flush operation by calling entity.flush() or even use entity.persistAndFlush() to make it a single method call. This will allow you to catch any PersistenceException that could occur when JPA send those changes to the database. Remember, this is less efficient so don’t abuse it. And your transaction still has to be committed.

    1. @Transactional
    2. public void create(Parameter parameter){
    3. try {
    4. //Here I use the persistAndFlush() shorthand method on a Panache repository to persist to database then flush the changes.
    5. return parameterRepository.persistAndFlush(parameter);
    6. }
    7. catch(PersistenceException pe){
    8. LOG.error("Unable to create the parameter", pe);
    9. //in case of error, I save it to disk
    10. diskPersister.save(parameter);
    11. }
    12. }

    Lock management

    Panache provides direct support for database locking with your entity/repository, using findById(Object, LockModeType) or find().withLock(LockModeType).

    The following examples are for the active record pattern, but the same can be used with repositories.

    First: Locking using findById().

    Second: Locking in a find().

    1. public class PersonEndpoint {
    2. @GET
    3. @Transactional
    4. public Person findByNameForUpdate(String name){
    5. Person p = Person.find("name", name).withLock(LockModeType.PESSIMISTIC_WRITE).findOne();
    6. //do something useful, the lock will be released when the transaction ends.
    7. return person;
    8. }
    9. }

    Be careful that locks are released when the transaction ends, so the method that invokes the lock query must be annotated with the @Transactional annotation.

    IDs are often a touchy subject, and not everyone’s up for letting them handled by the framework, once again we have you covered.

    You can specify your own ID strategy by extending PanacheEntityBase instead of PanacheEntity. Then you just declare whatever ID you want as a public field:

    1. public class Person extends PanacheEntityBase {
    2. @Id
    3. @SequenceGenerator(
    4. name = "personSequence",
    5. allocationSize = 1,
    6. initialValue = 4)
    7. @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "personSequence")
    8. public Integer id;
    9. //...
    10. }

    If you’re using repositories, then you will want to extend PanacheRepositoryBase instead of PanacheRepository and specify your ID type as an extra type parameter:

    1. @ApplicationScoped
    2. public class PersonRepository implements PanacheRepositoryBase<Person,Integer> {
    3. //...
    4. }

    Mocking

    If you are using the active record pattern you cannot use Mockito directly as it does not support mocking static methods, but you can use the quarkus-panache-mock module which allows you to use Mockito to mock all provided static methods, including your own.

    Add this dependency to your pom.xml:

    1. <dependency>
    2. <groupId>io.quarkus</groupId>
    3. <artifactId>quarkus-panache-mock</artifactId>
    4. <scope>test</scope>
    5. </dependency>

    Given this simple entity:

    1. @Entity
    2. public class Person extends PanacheEntity {
    3. public String name;
    4. public static List<Person> findOrdered() {
    5. return find("ORDER BY name").list();
    6. }
    7. }

    You can write your mocking test like this:

    1. @QuarkusTest
    2. public class PanacheFunctionalityTest {
    3. @Test
    4. public void testPanacheMocking() {
    5. PanacheMock.mock(Person.class);
    6. // Mocked classes always return a default value
    7. Assertions.assertEquals(0, Person.count());
    8. // Now let's specify the return value
    9. Mockito.when(Person.count()).thenReturn(23l);
    10. Assertions.assertEquals(23, Person.count());
    11. // Now let's change the return value
    12. Mockito.when(Person.count()).thenReturn(42l);
    13. Assertions.assertEquals(42, Person.count());
    14. // Now let's call the original method
    15. Mockito.when(Person.count()).thenCallRealMethod();
    16. Assertions.assertEquals(0, Person.count());
    17. // Check that we called it 4 times
    18. PanacheMock.verify(Person.class, Mockito.times(4)).count();(1)
    19. // Mock only with specific parameters
    20. Person p = new Person();
    21. Mockito.when(Person.findById(12l)).thenReturn(p);
    22. Assertions.assertSame(p, Person.findById(12l));
    23. Assertions.assertNull(Person.findById(42l));
    24. // Mock throwing
    25. Mockito.when(Person.findById(12l)).thenThrow(new WebApplicationException());
    26. Assertions.assertThrows(WebApplicationException.class, () -> Person.findById(12l));
    27. // We can even mock your custom methods
    28. Mockito.when(Person.findOrdered()).thenReturn(Collections.emptyList());
    29. Assertions.assertTrue(Person.findOrdered().isEmpty());
    30. // Mocking a void method
    31. Person.voidMethod();
    32. // Make it throw
    33. PanacheMock.doThrow(new RuntimeException("Stef2")).when(Person.class).voidMethod();
    34. try {
    35. Person.voidMethod();
    36. Assertions.fail();
    37. } catch (RuntimeException x) {
    38. Assertions.assertEquals("Stef2", x.getMessage());
    39. }
    40. // Back to doNothing
    41. PanacheMock.doNothing().when(Person.class).voidMethod();
    42. Person.voidMethod();
    43. // Make it call the real method
    44. PanacheMock.doCallRealMethod().when(Person.class).voidMethod();
    45. try {
    46. Person.voidMethod();
    47. Assertions.fail();
    48. } catch (RuntimeException x) {
    49. Assertions.assertEquals("void", x.getMessage());
    50. }
    51. PanacheMock.verify(Person.class).findOrdered();
    52. PanacheMock.verify(Person.class, Mockito.atLeast(4)).voidMethod();
    53. PanacheMock.verify(Person.class, Mockito.atLeastOnce()).findById(Mockito.any());
    54. PanacheMock.verifyNoMoreInteractions(Person.class);
    55. }
    56. }
    1Be sure to call your verify and do* methods on PanacheMock rather than Mockito, otherwise you won’t know what mock object to pass.

    Using the repository pattern

    If you are using the repository pattern you can use Mockito directly, using the quarkus-junit5-mockito module, which makes mocking beans much easier:

    1. <dependency>
    2. <groupId>io.quarkus</groupId>
    3. <artifactId>quarkus-junit5-mockito</artifactId>
    4. <scope>test</scope>
    5. </dependency>

    Given this simple entity:

    1. @Entity
    2. public class Person {
    3. @Id
    4. @GeneratedValue
    5. public Long id;
    6. public String name;
    7. }

    And this repository:

    1. @ApplicationScoped
    2. public class PersonRepository implements PanacheRepository<Person> {
    3. public List<Person> findOrdered() {
    4. return find("ORDER BY name").list();
    5. }
    6. }

    You can write your mocking test like this:

    1. @QuarkusTest
    2. public class PanacheFunctionalityTest {
    3. @InjectMock
    4. PersonRepository personRepository;
    5. @Test
    6. public void testPanacheRepositoryMocking() throws Throwable {
    7. // Mocked classes always return a default value
    8. Assertions.assertEquals(0, personRepository.count());
    9. // Now let's specify the return value
    10. Mockito.when(personRepository.count()).thenReturn(23l);
    11. Assertions.assertEquals(23, personRepository.count());
    12. // Now let's change the return value
    13. Mockito.when(personRepository.count()).thenReturn(42l);
    14. Assertions.assertEquals(42, personRepository.count());
    15. // Now let's call the original method
    16. Mockito.when(personRepository.count()).thenCallRealMethod();
    17. Assertions.assertEquals(0, personRepository.count());
    18. // Check that we called it 4 times
    19. Mockito.verify(personRepository, Mockito.times(4)).count();
    20. // Mock only with specific parameters
    21. Person p = new Person();
    22. Mockito.when(personRepository.findById(12l)).thenReturn(p);
    23. Assertions.assertSame(p, personRepository.findById(12l));
    24. Assertions.assertNull(personRepository.findById(42l));
    25. // Mock throwing
    26. Mockito.when(personRepository.findById(12l)).thenThrow(new WebApplicationException());
    27. Assertions.assertThrows(WebApplicationException.class, () -> personRepository.findById(12l));
    28. Mockito.when(personRepository.findOrdered()).thenReturn(Collections.emptyList());
    29. Assertions.assertTrue(personRepository.findOrdered().isEmpty());
    30. // We can even mock your custom methods
    31. Mockito.verify(personRepository).findOrdered();
    32. Mockito.verify(personRepository, Mockito.atLeastOnce()).findById(Mockito.any());
    33. Mockito.verifyNoMoreInteractions(personRepository);
    34. }
    35. }

    How and why we simplify Hibernate ORM mappings

    When it comes to writing Hibernate ORM entities, there are a number of annoying things that users have grown used to reluctantly deal with, such as:

    • Duplicating ID logic: most entities need an ID, most people don’t care how it’s set, because it’s not really relevant to your model.

    • Dumb getters and setters: since Java lacks support for properties in the language, we have to create fields, then generate getters and setters for those fields, even if they don’t actually do anything more than read/write the fields.

    • Traditional EE patterns advise to split entity definition (the model) from the operations you can do on them (DAOs, Repositories), but really that requires an unnatural split between the state and its operations even though we would never do something like that for regular objects in the Object Oriented architecture, where state and methods are in the same class. Moreover, this requires two classes per entity, and requires injection of the DAO or Repository where you need to do entity operations, which breaks your edit flow and requires you to get out of the code you’re writing to set up an injection point before coming back to use it.

    • Hibernate queries are super powerful, but overly verbose for common operations, requiring you to write queries even when you don’t need all the parts.

    • Hibernate is very general-purpose, but does not make it trivial to do trivial operations that make up 90% of our model usage.

    With Panache, we took an opinionated approach to tackle all these problems:

    • Make your entities extend PanacheEntity: it has an ID field that is auto-generated. If you require a custom ID strategy, you can extend PanacheEntityBase instead and handle the ID yourself.

    • Use public fields. Get rid of dumb getter and setters. Under the hood, we will generate all getters and setters that are missing, and rewrite every access to these fields to use the accessor methods. This way you can still write useful accessors when you need them, which will be used even though your entity users still use field accesses.

    • With the active record pattern: put all your entity logic in static methods in your entity class and don’t create DAOs. Your entity superclass comes with lots of super useful static methods, and you can add your own in your entity class. Users can just start using your entity Person by typing Person. and getting completion for all the operations in a single place.

    • Don’t write parts of the query that you don’t need: write Person.find("order by name") or Person.find("name = ?1 and status = ?2", "stef", Status.Alive) or even better Person.find("name", "stef").

    That’s all there is to it: with Panache, Hibernate ORM has never looked so trim and neat.

    Defining entities in external projects or jars

    Hibernate ORM with Panache relies on compile-time bytecode enhancements to your entities.

    It attempts to identity archives with Panache entities (and consumers of Panache entities) by the presence of the marker file META-INF/panache-archive.marker. Panache includes an annotation processor that will automatically create this file in archives that depend on Panache (even indirectly). If you have disabled annotation processors you may need to create this file manually in some cases.

    If you include the jpa-modelgen annotation processor this will exclude the Panache annotation processor by default. If you do this you should either create the marker file yourself, or add the quarkus-panache-common as well, as shown below:
    1. <plugin>
    2. <artifactId>maven-compiler-plugin</artifactId>
    3. <version>${compiler-plugin.version}</version>
    4. <configuration>
    5. <annotationProcessorPaths>
    6. <annotationProcessorPath>
    7. <groupId>org.hibernate</groupId>
    8. <artifactId>hibernate-jpamodelgen</artifactId>
    9. <version>${hibernate.version}</version>
    10. </annotationProcessorPath>
    11. <annotationProcessorPath>
    12. <groupId>io.quarkus</groupId>
    13. <artifactId>quarkus-panache-common</artifactId>
    14. <version>${quarkus.platform.version}</version>
    15. </annotationProcessorPath>
    16. </annotationProcessorPaths>