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

repository-pattern.mddocs/

Repository Pattern Operations

Repository-based persistence operations through PanacheRepository and PanacheRepositoryBase interfaces. This pattern provides clean separation of concerns by encapsulating entity operations within dedicated repository classes, following Domain-Driven Design principles.

Capabilities

Repository Interfaces

Foundation interfaces for the Repository pattern.

public interface PanacheRepository<Entity> extends PanacheRepositoryBase<Entity, Long> {
    // Repository for entities with Long ID - no additional methods
}

public interface PanacheRepositoryBase<Entity, Id> {
    // All default methods for repository operations
    
    // Entity Manager access
    default EntityManager getEntityManager();
    default Session getSession();
    
    // Instance operations
    default void persist(Entity entity);
    default void persistAndFlush(Entity entity);
    default void delete(Entity entity);
    default boolean isPersistent(Entity entity);
    default void flush();
}

Usage:

@ApplicationScoped
public class PersonRepository implements PanacheRepository<Person> {
    // Custom business methods
    public Person findByEmail(String email) {
        return find("email", email).firstResult();
    }
    
    public List<Person> findByStatus(PersonStatus status) {
        return list("status", status);
    }
}

// Injection and usage
@Inject
PersonRepository personRepository;

Person person = new Person();
personRepository.persist(person);

Finding Entities

Repository methods for finding entities by ID or query.

default Entity findById(Id id);

default Entity findById(Id id, LockModeType lockModeType);

default Optional<Entity> findByIdOptional(Id id);

default Optional<Entity> findByIdOptional(Id id, LockModeType lockModeType);

default PanacheQuery<Entity> find(String query, Object... params);

default PanacheQuery<Entity> find(String query, Sort sort, Object... params);

default PanacheQuery<Entity> find(String query, Map<String, Object> params);

default PanacheQuery<Entity> find(String query, Sort sort, Map<String, Object> params);

default PanacheQuery<Entity> find(String query, Parameters params);

default PanacheQuery<Entity> find(String query, Sort sort, Parameters params);

default PanacheQuery<Entity> findAll();

default PanacheQuery<Entity> findAll(Sort sort);

Usage:

@ApplicationScoped
public class PersonRepository implements PanacheRepository<Person> {
    
    public Person getPersonById(Long id) {
        return findById(id); // Returns null if not found
    }
    
    public Optional<Person> findPersonById(Long id) {
        return findByIdOptional(id); // Returns Optional
    }
    
    public List<Person> getActivePersons() {
        return find("status", PersonStatus.Active).list();
    }
    
    public PanacheQuery<Person> findByAgeRange(int minAge, int maxAge) {
        return find("age >= ?1 AND age <= ?2", minAge, maxAge);
    }
    
    public List<Person> getAllSorted() {
        return findAll(Sort.by("name", "age")).list();
    }
}

Listing Entities

Repository methods that directly return Lists (shortcuts for find().list()).

default List<Entity> list(String query, Object... params);

default List<Entity> list(String query, Sort sort, Object... params);

default List<Entity> list(String query, Map<String, Object> params);

default List<Entity> list(String query, Sort sort, Map<String, Object> params);

default List<Entity> list(String query, Parameters params);

default List<Entity> list(String query, Sort sort, Parameters params);

default List<Entity> listAll();

default List<Entity> listAll(Sort sort);

Usage:

@ApplicationScoped
public class PersonRepository implements PanacheRepository<Person> {
    
    public List<Person> getActivePersons() {
        return list("status", PersonStatus.Active);
    }
    
    public List<Person> getPersonsByCity(String city) {
        return list("city = :city", Parameters.with("city", city));
    }
    
    public List<Person> getAllPersonsSorted() {
        return listAll(Sort.by("lastName", "firstName"));
    }
    
    public List<Person> searchPersons(String namePattern, PersonStatus status) {
        Map<String, Object> params = Map.of(
            "pattern", "%" + namePattern + "%",
            "status", status
        );
        return list("name ILIKE :pattern AND status = :status", params);
    }
}

Streaming Entities

Repository methods that return Streams for memory-efficient processing (require active transaction).

default Stream<Entity> stream(String query, Object... params);

default Stream<Entity> stream(String query, Sort sort, Object... params);

default Stream<Entity> stream(String query, Map<String, Object> params);

default Stream<Entity> stream(String query, Sort sort, Map<String, Object> params);

default Stream<Entity> stream(String query, Parameters params);

default Stream<Entity> stream(String query, Sort sort, Parameters params);

default Stream<Entity> streamAll();

default Stream<Entity> streamAll(Sort sort);

Usage:

@ApplicationScoped
public class PersonRepository implements PanacheRepository<Person> {
    
    @Transactional
    public void processAllActivePersons() {
        stream("status", PersonStatus.Active)
            .forEach(this::processActivePerson);
    }
    
    @Transactional
    public List<String> getActivePersonEmails() {
        return stream("status = :status", Parameters.with("status", PersonStatus.Active))
            .map(person -> person.email)
            .filter(Objects::nonNull)
            .collect(Collectors.toList());
    }
    
    @Transactional
    public long countAdults() {
        return streamAll()
            .filter(person -> person.age >= 18)
            .count();
    }
}

Counting Entities

Repository methods for counting entities without loading them.

default long count();

default long count(String query, Object... params);

default long count(String query, Map<String, Object> params);

default long count(String query, Parameters params);

Usage:

@ApplicationScoped
public class PersonRepository implements PanacheRepository<Person> {
    
    public long getTotalPersonCount() {
        return count();
    }
    
    public long getActivePersonCount() {
        return count("status", PersonStatus.Active);
    }
    
    public long getPersonCountByCity(String city) {
        return count("city = :city", Parameters.with("city", city));
    }
    
    public long getAdultCount() {
        return count("age >= :minAge", Parameters.with("minAge", 18));
    }
}

Persisting Entities

Repository methods for saving entities to the database.

// Single entity persistence
default void persist(Entity entity);
default void persistAndFlush(Entity entity);

// Batch persistence
default void persist(Iterable<Entity> entities);
default void persist(Stream<Entity> entities);
default void persist(Entity firstEntity, Entity... entities);

Usage:

@ApplicationScoped
public class PersonRepository implements PanacheRepository<Person> {
    
    public void savePerson(Person person) {
        persist(person);
    }
    
    public void savePersonAndFlush(Person person) {
        persistAndFlush(person); // Immediately flushes to DB
    }
    
    public void saveMultiplePersons(List<Person> persons) {
        persist(persons); // Batch persist
    }
    
    public void savePersons(Person... persons) {
        persist(persons[0], Arrays.copyOfRange(persons, 1, persons.length));
    }
    
    @Transactional
    public Person createPerson(String name, String email) {
        Person person = new Person();
        person.name = name;
        person.email = email;
        person.status = PersonStatus.Active;
        persist(person);
        return person;
    }
}

Deleting Entities

Repository methods for removing entities from the database.

// Single entity deletion
default void delete(Entity entity);

// Bulk deletion methods
default long deleteAll();
default boolean deleteById(Id id);
default long delete(String query, Object... params);
default long delete(String query, Map<String, Object> params);
default long delete(String query, Parameters params);

Usage:

@ApplicationScoped
public class PersonRepository implements PanacheRepository<Person> {
    
    public void removePerson(Person person) {
        delete(person);
    }
    
    public boolean removePersonById(Long id) {
        return deleteById(id); // Returns true if entity was deleted
    }
    
    public long removeInactivePersons() {
        return delete("status", PersonStatus.Inactive);
    }
    
    public long removeOldPersons(LocalDate cutoffDate) {
        return delete("createdDate < :cutoff", 
            Parameters.with("cutoff", cutoffDate));
    }
    
    public void clearAllPersons() {
        deleteAll(); // Removes all entities
    }
}

Updating Entities

Repository methods for bulk update operations using HQL.

default int update(String query, Object... params);
default int update(String query, Map<String, Object> params);
default int update(String query, Parameters params);

Usage:

@ApplicationScoped
public class PersonRepository implements PanacheRepository<Person> {
    
    public int activatePersonsByCity(String city) {
        return update("status = ?1 where city = ?2", 
            PersonStatus.Active, city);
    }
    
    public int updatePersonStatus(PersonStatus oldStatus, PersonStatus newStatus) {
        return update("status = :newStatus where status = :oldStatus",
            Parameters.with("newStatus", newStatus).and("oldStatus", oldStatus));
    }
    
    public int markAdults() {
        return update("status = :adult where age >= :minAge",
            Parameters.with("adult", PersonStatus.Adult).and("minAge", 18));
    }
}

Entity Manager Access

Direct access to EntityManager and Session for advanced operations.

default EntityManager getEntityManager();
default Session getSession();
default void flush();

Usage:

@ApplicationScoped
public class PersonRepository implements PanacheRepository<Person> {
    
    public List<Person> findWithCriteria(String namePattern) {
        EntityManager em = getEntityManager();
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<Person> cq = cb.createQuery(Person.class);
        Root<Person> root = cq.from(Person.class);
        
        cq.select(root)
          .where(cb.like(root.get("name"), namePattern));
          
        return em.createQuery(cq).getResultList();
    }
    
    public List<Person> executeNativeQuery(String sql, Object... params) {
        Session session = getSession();
        Query<Person> query = session.createNativeQuery(sql, Person.class);
        for (int i = 0; i < params.length; i++) {
            query.setParameter(i + 1, params[i]);
        }
        return query.getResultList();
    }
    
    public void forceFlush() {
        flush(); // Forces pending changes to database
    }
}

Entity State Management

Repository methods for checking and managing entity persistence state.

default boolean isPersistent(Entity entity);

Usage:

@ApplicationScoped
public class PersonRepository implements PanacheRepository<Person> {
    
    public void saveIfNotPersistent(Person person) {
        if (!isPersistent(person)) {
            persist(person);
        }
    }
    
    public String getPersonState(Person person) {
        return isPersistent(person) ? "MANAGED" : "DETACHED";
    }
    
    @Transactional
    public Person saveOrUpdate(Person person) {
        if (isPersistent(person)) {
            // Already managed, changes will be auto-saved
            return person;
        } else {
            // Detached or new, need to persist
            persist(person);
            return person;
        }
    }
}

Repository Pattern Best Practices

Dependency Injection

// Inject repositories in service classes
@ApplicationScoped
public class PersonService {
    
    @Inject
    PersonRepository personRepository;
    
    @Inject
    AddressRepository addressRepository;
    
    @Transactional
    public Person createPersonWithAddress(PersonDto dto) {
        Person person = new Person();
        person.name = dto.name;
        person.email = dto.email;
        personRepository.persist(person);
        
        Address address = new Address();
        address.person = person;
        address.street = dto.street;
        addressRepository.persist(address);
        
        return person;
    }
}

Custom Repository Methods

@ApplicationScoped
public class PersonRepository implements PanacheRepository<Person> {
    
    // Business-specific finder methods
    public Optional<Person> findByEmail(String email) {
        return find("email", email).firstResultOptional();
    }
    
    public List<Person> findAdultsByCity(String city) {
        return list("age >= 18 AND city = ?1", city);
    }
    
    // Complex queries with custom logic
    public List<Person> findRecentlyActive(int days) {
        LocalDateTime cutoff = LocalDateTime.now().minusDays(days);
        return list("lastLoginDate >= ?1", cutoff);
    }
    
    // Validation and business rules
    @Transactional
    public Person createVerifiedPerson(String name, String email) {
        if (findByEmail(email).isPresent()) {
            throw new IllegalArgumentException("Email already exists");
        }
        
        Person person = new Person();
        person.name = name;
        person.email = email;
        person.status = PersonStatus.Verified;
        person.createdDate = LocalDateTime.now();
        
        persist(person);
        return person;
    }
}

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