Lesson 13. Quarkus Panache
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
Feature | PanacheEntity | PanacheRepository |
Pattern Used | Active Record | Repository Pattern |
Inheritance | Extends PanacheEntity | No inheritance required |
CRUD Methods | Provided by Panache | Provided via Repository |
Query Methods | Static methods on entity | Repository 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.