Jakarta Persistence (JPA)
Entities, fetch strategies, JPQL, transactions, and L2 cache discipline.
# CLAUDE.md — Jakarta Persistence (JPA)
## Entities
- One `@Entity` class per concept. Use a `@MappedSuperclass` for shared fields like timestamps and audit columns.
- Use surrogate keys (`@Id @GeneratedValue`) for new tables. Natural keys are valid only when they're truly stable.
- For UUIDs: `@Id @GeneratedValue(strategy = UUID) UUID id`.
- Annotate the table and column names explicitly when defaults disagree with your DB convention: `@Table(name = "users")`, `@Column(name = "created_at")`.
## Relationships
- Default to `@ManyToOne(fetch = LAZY)`. Eager fetch is a recipe for N+1 surprises.
- For `@OneToMany` and `@ManyToMany`, lazy is the default — keep it.
- Use `@JoinColumn` to control the foreign key column name. Use `@JoinTable` only for genuine many-to-many (no extra columns).
- For "association entities" (a join table with extra fields), promote it to its own `@Entity`.
## Fetching
- `JOIN FETCH` in JPQL when you need an association eagerly:
```java
TypedQuery<Order> q = em.createQuery("select o from Order o join fetch o.items where o.id = :id", Order.class);
```
- `@EntityGraph` for declarative fetch plans, attached per-query.
- Don't use `OpenSessionInView` or `OpenEntityManagerInView`. They turn N+1 from a bug into a slow default.
## Queries
- **JPQL** by default. Type-safe via `TypedQuery<T>`.
- **Criteria API** for dynamic queries. Verbose but type-safe.
- **Native SQL** (`createNativeQuery`) only when JPQL doesn't express what you need. Document why.
- Use named parameters (`:name`) — never positional in new code.
## Transactions
- `@Transactional` (Jakarta or Spring style depending on container) on service methods.
- Read-only transactions for queries — Hibernate skips dirty-checking.
- One unit-of-work per use case. Don't span transactions across HTTP requests.
## Caching
- **L1 (persistence context)** is per-transaction; you can't disable it.
- **L2 (shared cache)**: opt in per entity (`@Cacheable`). Use for read-heavy reference data.
- **Query cache** is rarely worth it — hit rates are low. Profile before enabling.
## Migrations
- **Flyway** or **Liquibase**. Don't let Hibernate auto-generate schema in anything beyond dev.
- `hibernate.hbm2ddl.auto=validate` in production to catch drift between schema and entities.
## Performance
- `@Where(clause = "deleted_at IS NULL")` for soft-delete filtering — applied to every query for the entity.
- Add indexes for every column you filter or sort on. JPA doesn't do it for you.
- For batch inserts, set `hibernate.jdbc.batch_size=50` and call `flush()` periodically.
## Don't
- Don't return entities from REST endpoints. Map to DTOs at the boundary so DB renames don't break the API.
- Don't lazy-load outside a transaction. `LazyInitializationException` is the most common JPA bug.
- Don't ignore the SQL log in dev (`hibernate.show_sql=true`). It's the fastest way to spot N+1.
- Don't share `EntityManager` instances across threads. They're not thread-safe.
Other Java templates
Modern Java Rules
Java 21+ defaults: records, sealed types, pattern matching, virtual threads, var.
Java Testing with JUnit 5
JUnit 5 + AssertJ + Mockito + Testcontainers — opinionated test conventions.
Java Virtual Threads & Concurrency
Project Loom virtual threads, structured concurrency, and modern concurrent patterns.
Java Build (Maven & Gradle)
Build conventions, dependency management, multi-module projects, and CI patterns.
Java Streams & Collections
Streams API, immutable collections, Collectors, and idiomatic data manipulation.