All Java templates

Jakarta Persistence (JPA)

Entities, fetch strategies, JPQL, transactions, and L2 cache discipline.

DevZone Tools1,480 copiesUpdated Apr 1, 2026Jakarta EEJava
# 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