Simplify your persistence code for Hibernate ORM via the active record or the repository pattern
—
Utility classes for transaction management, EntityManager access, parameter building, and sorting configuration. These utilities provide essential supporting functionality for Panache operations and query building.
The Panache utility class provides global operations for EntityManager access, transaction management, and database operations.
public class Panache {
// EntityManager access
public static EntityManager getEntityManager();
public static Session getSession();
public static EntityManager getEntityManager(Class<?> clazz);
public static Session getSession(Class<?> clazz);
public static EntityManager getEntityManager(String persistenceUnit);
public static Session getSession(String persistenceUnit);
// Transaction management
public static TransactionManager getTransactionManager();
public static void setRollbackOnly();
// Database operations
public static int executeUpdate(String query, Object... params);
public static int executeUpdate(String query, Map<String, Object> params);
public static int executeUpdate(String query, Parameters params);
// Flushing
public static void flush();
public static void flush(Class<?> clazz);
public static void flush(String persistenceUnit);
}Usage:
import io.quarkus.hibernate.orm.panache.Panache;
// Access default EntityManager/Session
EntityManager em = Panache.getEntityManager();
Session session = Panache.getSession();
// Access EntityManager for specific entity class
EntityManager personEm = Panache.getEntityManager(Person.class);
Session personSession = Panache.getSession(Person.class);
// Access EntityManager for specific persistence unit
EntityManager customEm = Panache.getEntityManager("custom-pu");
// Transaction management
TransactionManager tm = Panache.getTransactionManager();
Panache.setRollbackOnly(); // Mark transaction for rollback
// Execute update queries
int updated = Panache.executeUpdate("UPDATE Person SET status = ?1 WHERE age > ?2", PersonStatus.Adult, 18);
// Execute with named parameters
int deleted = Panache.executeUpdate("DELETE FROM Person WHERE status = :status",
Parameters.with("status", PersonStatus.Inactive));
// Manual flush operations
Panache.flush(); // Flush default EntityManager
Panache.flush(Person.class); // Flush EntityManager for Person
Panache.flush("custom-pu"); // Flush specific persistence unitUtility class for building named parameter maps in a fluent manner.
public class Parameters {
// Static factory method
public static Parameters with(String name, Object value);
// Builder methods
public Parameters and(String name, Object value);
// Convert to Map
public Map<String, Object> map();
}Usage:
import io.quarkus.panache.common.Parameters;
// Simple parameter building
Parameters params = Parameters.with("status", PersonStatus.Active)
.and("city", "NYC")
.and("minAge", 18);
// Use in queries
List<Person> persons = Person.list("status = :status AND city = :city AND age >= :minAge", params);
// Convert to Map if needed
Map<String, Object> paramMap = params.map();
// Complex parameter building
Parameters complexParams = Parameters.with("namePattern", "%John%")
.and("statuses", Arrays.asList(PersonStatus.Active, PersonStatus.Pending))
.and("startDate", LocalDate.now().minusYears(1))
.and("endDate", LocalDate.now());
List<Person> results = Person.list(
"name ILIKE :namePattern AND status IN :statuses AND createdDate BETWEEN :startDate AND :endDate",
complexParams
);Immutable utility class for representing pagination information.
public class Page {
// Fields
public final int index; // 0-based page index
public final int size; // Page size
// Constructors
public Page(int size);
public Page(int index, int size);
// Static factory methods
public static Page of(int index, int size);
public static Page ofSize(int size);
// Navigation methods
public Page next();
public Page previous();
public Page first();
public Page index(int newIndex);
}Usage:
import io.quarkus.panache.common.Page;
// Create page with default index 0
Page firstPage = new Page(20); // 20 items per page, page 0
Page firstPageAlt = Page.ofSize(20); // Same as above
// Create specific page
Page secondPage = new Page(1, 20); // Page 1 (second page), 20 items
Page secondPageAlt = Page.of(1, 20); // Same as above
// Navigation
Page page = Page.of(0, 10);
Page nextPage = page.next(); // Page(1, 10)
Page prevPage = nextPage.previous(); // Page(0, 10)
Page firstPage2 = page.first(); // Page(0, 10)
Page specificPage = page.index(5); // Page(5, 10)
// Use with queries
List<Person> persons = Person.findAll()
.page(Page.of(0, 25))
.list();
// Pagination loop
Page currentPage = Page.ofSize(100);
PanacheQuery<Person> query = Person.findAll();
while (true) {
List<Person> batch = query.page(currentPage).list();
if (batch.isEmpty()) break;
processBatch(batch);
currentPage = currentPage.next();
}Utility class for building SQL sorting specifications with direction and null precedence control.
public class Sort {
// Static factory methods
public static Sort by(String column);
public static Sort by(String column, Direction direction);
public static Sort by(String column, NullPrecedence nullPrecedence);
public static Sort by(String column, Direction direction, NullPrecedence nullPrecedence);
public static Sort by(String... columns);
public static Sort ascending(String... columns);
public static Sort descending(String... columns);
public static Sort empty();
// Builder methods
public Sort and(String name);
public Sort and(String name, Direction direction);
public Sort and(String name, NullPrecedence nullPrecedence);
public Sort and(String name, Direction direction, NullPrecedence nullPrecedence);
// Direction control
public Sort descending();
public Sort ascending();
public Sort direction(Direction direction);
// Configuration
public Sort disableEscaping();
// Access methods
public List<Column> getColumns();
public boolean isEscapingEnabled();
// Nested types
public enum Direction { Ascending, Descending }
public enum NullPrecedence { NULLS_FIRST, NULLS_LAST }
public static class Column {
public String getName();
public Direction getDirection();
public NullPrecedence getNullPrecedence();
}
}Usage:
import io.quarkus.panache.common.Sort;
// Simple sorting
Sort nameSort = Sort.by("name");
Sort nameDescSort = Sort.by("name", Sort.Direction.Descending);
// Multiple columns
Sort multiSort = Sort.by("lastName", "firstName");
Sort mixedSort = Sort.by("lastName").and("firstName", Sort.Direction.Descending);
// Convenience methods
Sort ascSort = Sort.ascending("name", "age");
Sort descSort = Sort.descending("createdDate", "id");
// Null precedence
Sort nullsFirstSort = Sort.by("name", Sort.NullPrecedence.NULLS_FIRST);
Sort complexSort = Sort.by("status", Sort.Direction.Ascending, Sort.NullPrecedence.NULLS_LAST)
.and("name");
// Direction changes
Sort sort = Sort.by("name", "age").descending(); // Makes both columns descending
Sort mixed = Sort.by("name").and("age").direction(Sort.Direction.Descending);
// Use with queries
List<Person> sorted = Person.listAll(Sort.by("lastName", "firstName"));
List<Person> complexSorted = Person.list("status = :status",
Parameters.with("status", PersonStatus.Active),
Sort.by("priority", Sort.Direction.Descending)
.and("createdDate", Sort.Direction.Ascending)
);
// Disable column escaping if needed
Sort unescaped = Sort.by("custom_column").disableEscaping();Combining utilities for complex operations:
public class PersonService {
@Transactional
public PagedResult<Person> searchPersonsPaginated(
String namePattern,
PersonStatus status,
String sortBy,
String sortDirection,
int page,
int size) {
// Build parameters
Parameters params = Parameters.with("namePattern", "%" + namePattern + "%")
.and("status", status);
// Build sort
Sort.Direction direction = "desc".equalsIgnoreCase(sortDirection)
? Sort.Direction.Descending
: Sort.Direction.Ascending;
Sort sort = Sort.by(sortBy, direction);
// Build page
Page pageObj = Page.of(page, size);
// Execute query
PanacheQuery<Person> query = Person.find("name ILIKE :namePattern AND status = :status", sort, params);
long totalCount = query.count();
List<Person> results = query.page(pageObj).list();
return new PagedResult<>(results, page, size, totalCount);
}
@Transactional
public void bulkUpdatePersonStatus(PersonStatus oldStatus, PersonStatus newStatus) {
// Use Panache utility for bulk update
int updated = Panache.executeUpdate(
"UPDATE Person SET status = :newStatus, updatedDate = :now WHERE status = :oldStatus",
Parameters.with("newStatus", newStatus)
.and("oldStatus", oldStatus)
.and("now", LocalDateTime.now())
);
// Force flush to ensure immediate update
Panache.flush();
log.info("Updated {} persons from {} to {}", updated, oldStatus, newStatus);
}
@Transactional
public void cleanupInactivePersons(int daysInactive) {
LocalDateTime cutoffDate = LocalDateTime.now().minusDays(daysInactive);
try {
// Get transaction manager for manual control
TransactionManager tm = Panache.getTransactionManager();
// Execute cleanup
int deleted = Panache.executeUpdate(
"DELETE FROM Person WHERE status = :status AND lastLoginDate < :cutoff",
Parameters.with("status", PersonStatus.Inactive)
.and("cutoff", cutoffDate)
);
if (deleted > 1000) {
// Too many deletions, rollback for safety
Panache.setRollbackOnly();
throw new IllegalStateException("Cleanup would delete too many records: " + deleted);
}
log.info("Cleaned up {} inactive persons", deleted);
} catch (Exception e) {
Panache.setRollbackOnly();
throw new RuntimeException("Cleanup failed", e);
}
}
public List<Person> getTopPersonsByActivity(int limit) {
// Complex sorting with null handling
Sort activitySort = Sort.by("lastLoginDate", Sort.Direction.Descending, Sort.NullPrecedence.NULLS_LAST)
.and("createdDate", Sort.Direction.Descending);
return Person.find("status = :status", Parameters.with("status", PersonStatus.Active))
.page(0, limit)
.list();
}
@Transactional
public void processPersonsBatch(PersonStatus status, int batchSize) {
// Use multiple persistence units if needed
EntityManager mainEm = Panache.getEntityManager();
EntityManager reportEm = Panache.getEntityManager("reporting");
Page currentPage = Page.ofSize(batchSize);
PanacheQuery<Person> query = Person.find("status", status);
int processedCount = 0;
while (true) {
List<Person> batch = query.page(currentPage).list();
if (batch.isEmpty()) break;
// Process batch
for (Person person : batch) {
processActivePerson(person);
processedCount++;
}
// Flush every batch
Panache.flush();
// Move to next page
currentPage = currentPage.next();
}
log.info("Processed {} persons with status {}", processedCount, status);
}
}Common patterns for using utilities safely:
public class UtilityBestPractices {
// Safe parameter building
public Parameters buildSafeParameters(Map<String, Object> input) {
Parameters params = null;
for (Map.Entry<String, Object> entry : input.entrySet()) {
if (entry.getValue() != null) { // Skip null values
if (params == null) {
params = Parameters.with(entry.getKey(), entry.getValue());
} else {
params = params.and(entry.getKey(), entry.getValue());
}
}
}
return params != null ? params : Parameters.with("dummy", "dummy"); // Ensure not null
}
// Safe sort building
public Sort buildSafeSort(String sortBy, String direction) {
if (sortBy == null || sortBy.trim().isEmpty()) {
return Sort.by("id"); // Default sort
}
// Validate sort column to prevent SQL injection
if (!isValidColumnName(sortBy)) {
throw new IllegalArgumentException("Invalid sort column: " + sortBy);
}
Sort.Direction dir = "desc".equalsIgnoreCase(direction)
? Sort.Direction.Descending
: Sort.Direction.Ascending;
return Sort.by(sortBy, dir);
}
// Safe pagination
public Page buildSafePage(Integer page, Integer size) {
int safePage = (page != null && page >= 0) ? page : 0;
int safeSize = (size != null && size > 0 && size <= 1000) ? size : 20; // Max 1000
return Page.of(safePage, safeSize);
}
// Transaction-safe operations
@Transactional
public void safeExecuteUpdate(String query, Parameters params) {
try {
int updated = Panache.executeUpdate(query, params);
if (updated == 0) {
log.warn("No rows updated by query: {}", query);
} else {
log.info("Updated {} rows", updated);
}
} catch (Exception e) {
Panache.setRollbackOnly();
log.error("Update failed, transaction marked for rollback", e);
throw e;
}
}
private boolean isValidColumnName(String columnName) {
// Simple validation - extend as needed
return columnName.matches("^[a-zA-Z_][a-zA-Z0-9_]*$");
}
}Install with Tessl CLI
npx tessl i tessl/maven-io-quarkus--quarkus-hibernate-orm-panache