CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-quarkus--quarkus-hibernate-orm-panache

Simplify your persistence code for Hibernate ORM via the active record or the repository pattern

Pending
Overview
Eval results
Files

utilities.mddocs/

Utility Operations

Utility classes for transaction management, EntityManager access, parameter building, and sorting configuration. These utilities provide essential supporting functionality for Panache operations and query building.

Capabilities

Panache Utility Class

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 unit

Parameters Builder

Utility 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
);

Page Class

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();
}

Sort Class

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();

Advanced Utility Usage Examples

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);
    }
}

Error Handling and Best Practices

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

docs

active-record.md

index.md

query-pagination.md

repository-pattern.md

utilities.md

tile.json