jakartaee / jakartaee/persistence

Call-site fetch control: type-safe, composable fetch paths on EntityManager and TypedQuery

Open
#978 3 comments 0 reactions 0 assignees View on GitHub
candidate-for-4.1
Dominant language
Java
Stars
268
Forks
78
Avg merge
1d 6h
Merged PRs (30d)
13

Description

## Problem

JPA's `EntityGraph` API is the right mechanism for controlling association fetch depth at the call site, but its API is less developer-friendly than it could be.

The JPA static metamodel (`Person_`, `Organization_`) provides type-safe attribute references, but each metamodel attribute is an isolated constant with no way to express traversal as a single composable expression. Building an `EntityGraph` still requires verbose, manual tree construction:
```java
EntityGraph graph = em.createEntityGraph(Person.class);
graph.addSubgraph(Person_.organization)
.addAttributeNodes(Organization_.country);
em.find(Person.class, id, Map.of("jakarta.persistence.fetchgraph", graph));
```

Every additional hop requires another `addSubgraph` call. Combining independent paths (e.g. `organization.country` and `role`) means building the tree manually. There is no way to express a multi-segment path as a single composable unit.

The cumulative effect is that `EntityGraph` is harder to use than it should be. Rather than expressing fetch intent directly as path chains, developers must translate that intent into a manually constructed subgraph tree — a mismatch that makes the API less intuitive than the underlying concept.

---

## Proposal

Two complementary additions to the spec.

### 1. Composable metamodel path expressions

Extend the static metamodel so that association attributes expose further traversal, producing a single expression that describes a full path:
```java
// Single-segment
Person_.organization // → Organization

// Multi-segment — organization → country
Person_.organization.country() // → Country

// Independent paths
Person_.organization.country()
Person_.role
```

The exact mechanism — whether via generated methods on metamodel types, a new `Path` wrapper type, or another approach — is left open for discussion. The key requirement is that multi-segment paths are expressible as a single composable unit, validated at compile time.

### 2. `setFetchPaths` on `TypedQuery` and `EntityManager.find`

Add a first-class method for specifying fetch paths at the call site, replacing the current hint-based approach:
```java
// EntityManager.find
Person person = em.find(Person.class, id,
Person_.organization.country(),
Person_.role);

// TypedQuery
List persons = em.createQuery("select p from Person p where p.name = ?1", Person.class)
.setParameter(1, "Smith")
.setFetchPaths(Person_.organization.country(), Person_.role)
.getResultList();
```

The implementation would translate path expressions into `EntityGraph` subgraphs internally, merging shared prefixes automatically. Callers would never need to construct an `EntityGraph` directly.

---

## Prior art

This pattern has been validated in production since 2015 and is available as an open-source reference implementation: [jpa-fetch](https://github.com/alterioncorp/jpa-fetch). It implements both a JPA metamodel style (`FetchPaths.of(Person_.organization, Organization_.country)`) and a QueryDSL style (`QPerson.person.organization().country()`), built entirely on existing JPA primitives. The library demonstrates that the `EntityGraph` construction and prefix-merging logic is straightforward to implement once callers have a composable path API to work with.

---

## Notes

- The composable metamodel path (proposal 1) is the more foundational change and would be valuable independent of proposal 2.
- `setFetchPaths` on `TypedQuery` is a natural extension — fetch control belongs at the query execution site, not scattered across `@NamedEntityGraph` annotations on entity classes.
- Both proposals are backward compatible — no existing API is changed.

Contributor guide

Open the contributing guide

Research direction

Start by reviewing the EntityGraph API, the static metamodel, EntityManager.find, and TypedQuery, then compare the jpa-fetch reference implementation linked in the issue. The open design question is how to represent composable paths; done means the specification defines that mechanism and the fetch-path methods, including EntityGraph translation, prefix merging, and backward compatibility.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
api, backend
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.