Quarkus + Hibernate ORM with Panache
Active-record and repository styles with Panache, Flyway migrations, transactions.
# CLAUDE.md — Quarkus + Hibernate ORM with Panache
## Pick a style
- **Active record**: entity extends `PanacheEntity` and exposes `find()`, `persist()`, `count()` directly on the class.
- **Repository**: separate `PanacheRepository<E>` class; entities stay POJOs.
- Pick one per project. Mixing is confusing.
- For DDD-style codebases, repositories are usually the right call. For CRUD apps, active record is faster to write.
## Entities
- Use records-friendly entity patterns:
```java
@Entity
public class User extends PanacheEntity {
public String email;
public String name;
public Instant createdAt = Instant.now();
}
```
- Public fields are fine — Panache rewrites them to property accessors at build time.
- Use `@Id` + `@GeneratedValue` only if you need a custom ID strategy. Default is `Long id`.
## Queries
- Derived queries via static methods on the entity (active record):
```java
public static List<User> findByEmail(String email) { return list("email", email); }
```
- Or via repository methods:
```java
@ApplicationScoped
public class UserRepository implements PanacheRepository<User> {
public List<User> findByEmail(String email) { return list("email", email); }
}
```
- Use `find(...)` for lazy queries, `list(...)` for eager. `firstResult()` / `singleResult()` to materialize.
- Pagination: `find("...").page(Page.of(0, 20)).list()`.
## Transactions
- `@Transactional` on the service method. Don't put it on entities or repositories.
- Reads don't need a transaction unless they touch lazy collections — use `@Transactional` defensively if you're not sure.
- For **reactive Panache**, use `@WithTransaction` on `Uni`-returning methods.
## Migrations
- **Flyway** (`quarkus-flyway`) or **Liquibase** (`quarkus-liquibase`). Pick one.
- `quarkus.flyway.migrate-at-start=true` for dev. In production, run migrations as a separate step.
- Don't rely on `quarkus.hibernate-orm.database.generation=update` outside dev — it's not safe for production.
## DTO projection
- For list views, project to a DTO instead of returning entities:
```java
public static List<UserListDto> listUsers() {
return find("select new com.app.UserListDto(id, email) from User").project(UserListDto.class).list();
}
```
- Avoid loading associations you don't render.
## Reactive variant
- Use `quarkus-hibernate-reactive-panache` for non-blocking DB access.
- Pair with `vertx-pg-client` driver. SQL drivers are blocking and won't work.
- Methods return `Uni<T>` and `Multi<T>`. Lifecycle integrates with RESTEasy Reactive endpoints.
## Don't
- Don't use Panache active record for CRUD-heavy domain models with rich behavior. They get crowded fast.
- Don't put query logic in resources. Push it to the entity (active record) or repository.
- Don't share the same entity class between two databases. Use separate persistence units.
- Don't use the blocking and reactive Panache variants in the same project.
Other Jakarta EE templates
Modern Quarkus Rules
Quarkus 3+ defaults: dev mode, build-time optimization, configuration, and CDI.
Quarkus REST (RESTEasy Reactive)
JAX-RS endpoints with RESTEasy Reactive — request validation and response models.
Modern Jakarta EE Rules
Jakarta EE 10+ namespace migration, modular architecture, and platform conventions.
Jakarta CDI
CDI scopes, qualifiers, producers, events, and the lifecycle to actually understand.
Jakarta Persistence (JPA)
Entities, fetch strategies, JPQL, transactions, and L2 cache discipline.