Load a required to-one association with the receipt query instead of discovering an N+1 after mapping begins.
Spring Data JPA entity graph for a bounded to-one read
Make the read shape explicit
A receipt detail endpoint returns 47 rows and shows each account name. If mapping touches receipt.account and the association was left lazy, the ORM can issue one root query plus a separate account query for each uncached association. A repository method with an entity graph requests the account in the same read plan. The method remains tenant-scoped. This is a narrow application of fetch-plan design, not a reason to mark every association eager.
Keep pagination away from collection fetches
A to-one graph has at most one account per receipt, so it does not multiply receipt rows. A collection graph is different: joining receipt lines can multiply rows, interfere with page boundaries, or force expensive deduplication. For a list endpoint that needs a collection, page receipt IDs first and fetch details for those bounded IDs in a second query. Page and Slice still need a stable order.
Verify the actual SQL
Run the endpoint against 47 receipts that reference different accounts. Capture statement count and compare it with the query without a graph. Assert the response still contains only the authenticated tenant's receipts. An entity graph chooses fetch shape; it does not add an authorization predicate or guarantee one SQL statement under every provider and mapping.
Implementation contract
interface ReceiptReadRepository extends JpaRepository<ReceiptEntity, UUID> {
@EntityGraph(attributePaths = "account")
List<ReceiptEntity> findTop47ByTenantIdOrderByCreatedAtDescIdDesc(
UUID tenantId);
}Cost and verification
A to-one join transfers account columns with each receipt row. That is often cheaper than dozens of round trips, but wider rows can be wasteful when the response does not need account data. Compare SQL and payload size on a representative read.
Common Mistakes
- Do not use a collection entity graph on a paged query without checking row multiplication and page correctness.
- Do not assume a graph filters another tenant's rows; keep the tenant predicate.
- Do not replace SQL inspection with a repository mock.
Read next
Spring JPA fetch plans: measure N+1 queries before changing mappings, Spring Data JPA Page versus Slice: pay for totals only when needed, Spring Boot Open EntityManager in View: close the response-time query gap, Spring JdbcTemplate tenant predicates: put ownership in the SQL query, Spring Data JPA projections: return selected fields without a full entity.
