N+1 problem対策 context aside, here’s the translation.
Many people have written @EntityGraph(attributePaths = {"items"}) directly on a Repository method as a countermeasure for the N+1 problem. The underlying mechanics of the N+1 problem itself are covered in the Spring Data JPA N+1 Problem Resolution Guide, but this approach has a weakness. When you copy-paste the same attributePaths across multiple methods, it’s common to add a new association and forget to update one of the methods.
@NamedEntityGraph solves this problem. By defining it once on the entity class, you can reference it by the same name from any Repository method.
Basic syntax of @NamedEntityGraph
The minimal setup looks like this. name is the identifier, and attributeNodes specifies what to fetch.
@Entity
@NamedEntityGraph(
name = "Order.withItems",
attributeNodes = {
@NamedAttributeNode("items")
}
)
public class Order {
@Id
private Long id;
@OneToMany(mappedBy = "order")
private List<OrderItem> items;
// ...
}
Since name is the key referenced by string from the Repository side, naming it like EntityName.purpose makes it easier to understand at a glance later.
Specifying multiple attributes together with @NamedAttributeNode
attributeNodes accepts an array, so you can list multiple fields. It’s fine to mix OneToMany and ManyToOne.
@NamedEntityGraph(
name = "Order.withItemsAndCustomer",
attributeNodes = {
@NamedAttributeNode("items"),
@NamedAttributeNode("customer")
}
)
That said, specifying too many ends up increasing JOINs and making the query heavier, so it’s worth asking each time “does this screen really need this association?”
Fetching nested associations with @NamedSubgraph
When you want to specify an association two or more levels deep—for example, fetching all the way to product beyond items—combine the subgraph attribute with @NamedSubgraph.
@NamedEntityGraph(
name = "Order.withItemsAndProduct",
attributeNodes = {
@NamedAttributeNode(value = "items", subgraph = "itemsWithProduct")
},
subgraphs = {
@NamedSubgraph(
name = "itemsWithProduct",
attributeNodes = {
@NamedAttributeNode("product")
}
)
}
)
Fetching all the way to grandchild entities adds more JOINs as well, so it’s recommended to limit deep subgraphs to only the levels you truly need.
Centrally managing multiple graph definitions with @NamedEntityGraphs
If you want to have multiple graphs for different use cases, you can define them together with @NamedEntityGraphs.
@Entity
@NamedEntityGraphs({
@NamedEntityGraph(
name = "Order.withItems",
attributeNodes = { @NamedAttributeNode("items") }
),
@NamedEntityGraph(
name = "Order.withCustomer",
attributeNodes = { @NamedAttributeNode("customer") }
)
})
public class Order {
// ...
}
Giving each one a distinct name per screen or use case, like Order.withItems and Order.withCustomer, makes it less likely for the Repository side to reference the wrong one.
The difference between EntityGraphType.LOAD and FETCH
This is a subtly important point. Both types JOIN FETCH the attributes included in the graph, but the handling of attributes not included in the graph differs.
FETCH: Attributes not included in the graph are forcibly treated as LAZYLOAD: Attributes not included in the graph respect the entity’s existing settings (the LAZY/EAGER definitions already in place) as-is
A good rule of thumb: choose LOAD if you want to additionally fetch only the associations specified in the graph without disrupting the existing LAZY/EAGER settings, and choose FETCH if you want the graph’s contents to fully reveal the fetch scope at a glance.
-- LOAD: EAGER attributes outside the graph are still JOINed as-is
select o.*, i.*, c.* from orders o
left join order_item i on i.order_id = o.id
left join customer c on c.id = o.customer_id
-- FETCH: EAGER attributes outside the graph are also treated as LAZY and not additionally JOINed
select o.*, i.* from orders o
left join order_item i on i.order_id = o.id
Applying @NamedEntityGraph to Repository methods
Once a graph is defined, you can use it by attaching @EntityGraph to a Repository method and specifying the name in value. It can be applied directly to existing derived query methods as well.
public interface OrderRepository extends JpaRepository<Order, Long> {
@EntityGraph(value = "Order.withItems", type = EntityGraph.EntityGraphType.LOAD)
Optional<Order> findById(Long id);
@EntityGraph(value = "Order.withItemsAndCustomer", type = EntityGraph.EntityGraphType.FETCH)
List<Order> findByStatus(OrderStatus status);
}
By specifying the same value across multiple methods, the fetch definition stays consolidated in one place on the entity side. Even when adding a new association, you only need to update the entity’s annotation—there’s no need to hunt down and fix each calling method one by one.
Criteria for choosing between ad-hoc @EntityGraph and NamedEntityGraph
You don’t need to use @NamedEntityGraph every single time. In practice, the following rough guideline should suffice:
- If the same fetch definition is used in two or more methods, consider
@NamedEntityGraph - For simple cases used only once, an ad-hoc
@EntityGraph(attributePaths = {...})is sufficient - If you want to leave a record on the entity side of “what does this method fetch together,”
@NamedEntityGraphimproves readability
If you’re still unsure how to write query methods themselves, the Spring Data JPA Query Methods Guide may also be helpful.
Summary
@NamedEntityGraph is meant to organize the ad-hoc @EntityGraph usage you relied on for N+1 problem countermeasures into a form that can be reused across multiple locations. Define the fetch scope with attributeNodes and subgraphs, decide how attributes outside the graph are handled with EntityGraphType, and pass only the name to the Repository method. Once you remember this flow, adding more related entities only requires updating the entity class side.
If you’d also like to revisit your entity association design itself, check out the article on entity relationship mapping as well.