Simplify your persistence code for Hibernate ORM via the active record or the repository pattern
—
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.
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);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();
}
}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);
}
}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();
}
}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));
}
}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;
}
}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
}
}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));
}
}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
}
}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;
}
}
}// 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;
}
}@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