Lesson 13. Quarkus Panache

ยท

4 min read

In Quarkus, Panache is a simplified and opinionated ORM (Object-Relational Mapping) layer built on Hibernate ORM. It provides an active record pattern and repository pattern to simplify database interactions.

1. Panache in Quarkus

Panache is part of Quarkus Hibernate ORM and aims to simplify JPA (Java Persistence API) by reducing boilerplate code for entity management.

It provides:

  • Active Record Pattern: Where an entity extends PanacheEntity.

  • Repository Pattern: Where an entity uses PanacheRepository.


2. PanacheEntity

PanacheEntity is a base class for JPA entities that automatically provides CRUD operations.

Example using PanacheEntity (Active Record Pattern)

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

@Entity
public class User extends PanacheEntity {
    public String name;
    public int age;

    // Query method using Panache
    public static User findByName(String name) {
        return find("name", name).firstResult();
    }
}

Features of PanacheEntity:

  • Provides an auto-generated id field.

  • Has built-in CRUD operations like:

      User user = User.findById(1L); // Find user by ID
      user.delete(); // Delete user
      List<User> users = User.listAll(); // List all users
    
  • Supports queries using simple syntax:

      User user = User.find("name", "John").firstResult();
    

3. PanacheEntityBase

If you don't want to extend PanacheEntity, you can use PanacheEntityBase and manually define id:

import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Product extends PanacheEntityBase {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Long id;

    public String name;
    public double price;
}

4. PanacheRepository

If you prefer the Repository Pattern instead of extending PanacheEntity, you can use PanacheRepository:

import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class UserRepository implements PanacheRepository<User> {
    public User findByName(String name) {
        return find("name", name).firstResult();
    }
}

Usage:

@Inject
UserRepository userRepository;

User user = userRepository.findById(1L);

5. Key Differences

FeaturePanacheEntityPanacheRepository
Pattern UsedActive RecordRepository Pattern
InheritanceExtends PanacheEntityNo inheritance required
CRUD MethodsProvided by PanacheProvided via Repository
Query MethodsStatic methods on entityRepository methods

6. Custom Queries with Panache

Panache allows defining custom queries with a fluent API or named queries.

Example: Find users older than a specific age

List<User> users = User.find("age > ?1", 18).list();

Or using a named query:

List<User> users = User.find("from User u where u.age > ?1", 18).list();

Dynamic Queries with Parameters

You can dynamically construct queries:

User.find("name = ?1 and age > ?2", "Alice", 25).firstResult();

7. Pagination in Panache

Pagination helps in fetching large datasets efficiently.

Example: Fetching results page by page

List<User> users = User.find("age > ?1", 18).page(0, 10).list();
  • page(0, 10): Fetches first 10 results.

  • page(1, 10): Fetches next 10 results.


8. Native Queries in Panache

You can execute raw SQL queries when needed.

Example: Using native SQL

List<User> users = User.find("#User.getAllAdults").list();
@NamedQuery(name = "User.getAllAdults", query = "SELECT u FROM User u WHERE u.age >= 18")

For dynamic queries:

List<User> users = User.find("SELECT * FROM users WHERE age > ?1", 18).list();

9. Transactions in Panache

Panache automatically manages transactions, but you can explicitly control them using @Transactional.

Example: Updating a user inside a transaction

import jakarta.transaction.Transactional;

@Transactional
public void updateUserName(Long id, String newName) {
    User user = User.findById(id);
    if (user != null) {
        user.name = newName;
    }
}
  • The method executes in a transaction.

  • If an exception occurs, the transaction rolls back.


10. Performance Optimization in Panache

Panache provides several optimizations for better performance.

Fetching Only Required Columns

List<User> users = User.find("SELECT name FROM User").list();
  • Reduces memory usage by fetching only required fields.

Indexing Queries for Performance

Use indexes on frequently queried columns:

@Table(indexes = @Index(columnList = "name"))
@Entity
public class User extends PanacheEntity {
    public String name;
}

Would you like to see real-world examples with Panache? ๐Ÿš€

11. When to Use What?

  • Use PanacheEntity when:

    • You prefer an Active Record style (e.g., User.findById(1L)).

    • Your application is simple and does not require custom repositories.

  • Use PanacheRepository when:

    • You prefer a Repository Pattern for better separation of concerns.

    • You need a more structured approach with dependency injection.


Conclusion

  • PanacheEntity makes CRUD operations simpler by providing built-in methods.

  • PanacheRepository is useful when you want full control over your repository logic.

  • Panache removes boilerplate code compared to standard JPA + Hibernate.

ย