Spring Data JPA
Repositories, derived queries, custom JPQL, projections, and N+1 prevention.
# CLAUDE.md — Spring Data JPA
## Repositories
- Extend `JpaRepository<Entity, Id>` for the standard CRUD surface. `CrudRepository` is the lowest common denominator.
- Don't write methods you don't use. The auto-generated query is only created when called.
- One repository per aggregate root. Don't share a `UserRepository` across unrelated services.
## Derived queries
- Use derived query methods for simple cases: `findByEmail`, `findByStatusAndCreatedAtAfter`.
- When the method name gets longer than ~50 chars, switch to `@Query`.
- `@Query("...")` for explicit JPQL. Use named parameters (`:name`), not positional.
- `@Query(nativeQuery = true)` only when JPQL can't express it. Document why.
## Projections
- Read just the columns you need. Don't fetch full entities for a list view.
- Interface projections — Spring builds the proxy from getter signatures:
```java
interface UserListView {
String getId();
String getEmail();
Instant getCreatedAt();
}
List<UserListView> findAllProjectedBy();
```
- DTO projections in JPQL: `select new com.app.UserDto(u.id, u.email) from User u`.
## N+1 prevention
- `@EntityGraph` on repository methods to fetch associations eagerly when you need them:
```java
@EntityGraph(attributePaths = {"orders", "address"})
List<User> findAll();
```
- For collection associations, prefer `JOIN FETCH` in `@Query` over `EntityGraph` for control over the join type.
- Profile every list endpoint. **Hibernate Statistics** or **DataSource Proxy** in dev catch N+1s before prod.
## Pagination
- `Pageable` parameter on repository methods. Spring derives `LIMIT` / `OFFSET`.
- Cursor-based pagination via slice queries (`Slice<T> findFirst20ByOrderByIdAscIdGreaterThan(String cursor)`) for large datasets.
- Don't use `OFFSET` for deep pagination — performance degrades quadratically.
## Transactions
- `@Transactional` on service methods, not on repositories. Repositories run inside the caller's transaction.
- `readOnly = true` on queries. Hibernate skips dirty-checking and flush.
- Don't lazy-load attributes outside the transaction (`LazyInitializationException`). Either fetch them eagerly with `EntityGraph` or use a projection.
## Auditing
- `@CreatedDate`, `@LastModifiedDate`, `@CreatedBy`, `@LastModifiedBy` for free with `@EnableJpaAuditing`.
- One `AuditorAware` bean to expose the current user. Don't repeat in every entity.
- Add `@EntityListeners(AuditingEntityListener.class)` to the entity (or a `@MappedSuperclass`).
## Migrations
- **Flyway** or **Liquibase**. Don't rely on `spring.jpa.hibernate.ddl-auto=update` in production.
- `ddl-auto=validate` in production to assert the schema matches.
- Run migrations as a separate step before app boot in multi-instance deploys.
## Don't
- Don't use `findAll()` without pagination on tables that grow.
- Don't return `Optional<List<T>>` — use an empty list.
- Don't expose JPA entities through controllers. Map to DTOs at the boundary.
- Don't catch `EntityNotFoundException` in services to translate it. Use a `Optional` return or throw a domain exception.
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.