Creating Fetch Plans with Entity Graphs

We are working on a fresh, updated Jakarta EE Tutorial. This section hasn’t yet been updated.

This chapter explains how to use entity graphs to create fetch plans for Jakarta Persistence operations and queries.

Overview of Using Fetch Plans and Entity Graphs

Entity graphs are templates for a particular Persistence query or operation. They are used when creating fetch plans, or groups of persistent fields that are retrieved at the same time. Application developers use fetch plans to group together related persistent fields to improve runtime performance.

By default, entity fields or properties are fetched lazily. Developers specify fields or properties as part of a fetch plan, and the persistence provider will fetch them eagerly.

For example, an email application that stores messages as EmailMessage entities prioritizes fetching some fields over others. The sender, subject, and date will be viewed the most often, in mailbox views and when the message is displayed. The EmailMessage entity has a collection of related EmailAttachment entities. For performance reasons the attachments should not be fetched until they are needed, but the file names of the attachment are important. A developer working on this application might make a fetch plan that eagerly fetches the important fields from EmailMessage and EmailAttachment while fetching the lower priority data lazily.

Entity Graph Basics

You can create entity graphs statically by using annotations or a deployment descriptor, or dynamically by using standard interfaces.

You can use an entity graph with the EntityManager.find method or as part of a JPQL or Criteria API query by specifying the entity graph as a hint to the operation or query.

Entity graphs have attributes that correspond to the fields that will be eagerly fetched during a find or query operation. The primary key and version fields of the entity class are always fetched and do not need to be explicitly added to an entity graph.

The Default Entity Graph

By default, all fields in an entity are fetched lazily unless the fetch attribute of the entity metadata is set to jakarta.persistence.FetchType.EAGER. The default entity graph consists of all the fields of an entity whose fields are set to be eagerly fetched.

For example, the following EmailMessage entity specifies that some fields will be fetched eagerly:

@Entity
public class EmailMessage implements Serializable {
    @Id
    String messageId;
    @Basic(fetch=EAGER)
    String subject;
    String body;
    @Basic(fetch=EAGER)
    String sender;
    @OneToMany(mappedBy="message", fetch=LAZY)
    Set<EmailAttachment> attachments;
    ...
}

The default entity graph for this entity would contain the messageId, subject, and sender fields, but not the body or attachments fields.

Using Entity Graphs in Persistence Operations

Entity graphs are used by creating an instance of the jakarta.persistence.EntityGraph interface by calling either EntityManager.getEntityGraph for named entity graphs or EntityManager.createEntityGraph for creating dynamic entity graphs.

A named entity graph is an entity graph specified by the @NamedEntityGraph annotation applied to entity classes, or the named-entity-graph element in the application’s deployment descriptors. Named entity graphs defined within the deployment descriptor override any annotation-based entity graphs with the same name.

The created entity graph can be either a fetch graph or a load graph.

Fetch Graphs

To specify a fetch graph, set the jakarta.persistence.fetchgraph property when you execute an EntityManager.find or query operation. A fetch graph consists of only the fields explicitly specified in the EntityGraph instance, and ignores the default entity graph settings.

In the following example, the default entity graph is ignored, and only the body field is included in the dynamically created fetch graph:

EntityGraph<EmailMessage> eg = em.createEntityGraph(EmailMessage.class);
eg.addAttributeNodes("body");
...
Properties props = new Properties();
props.put("jakarta.persistence.fetchgraph", eg);
EmailMessage message = em.find(EmailMessage.class, id, props);

Load Graphs

To specify a load graph, set the jakarta.persistence.loadgraph property when you execute an EntityManager.find or query operation. A load graph consists of the fields explicitly specified in the EntityGraph instance plus any fields in the default entity graph.

In the following example, the dynamically created load graph contains all the fields in the default entity graph plus the body field:

EntityGraph<EmailMessage> eg = em.createEntityGraph(EmailMessage.class);
eg.addAttributeNodes("body");
...
Properties props = new Properties();
props.put("jakarta.persistence.loadgraph", eg);
EmailMessage message = em.find(EmailMessage.class, id, props);

Using Named Entity Graphs

Named entity graphs are created using annotations applied to entity classes or the named-entity-graph element and its sub-elements in the application’s deployment descriptor. The persistence provider will scan for all named entity graphs, defined in both annotations and in XML, within an application. A named entity graph set using an annotation may be overridden using named-entity-graph.

Applying Named Entity Graph Annotations to Entity Classes

The jakarta.persistence.NamedEntityGraph annotation defines a single named entity graph and is applied at the class level. Multiple @NamedEntityGraph annotations may be defined for a class by adding them within a jakarta.persistence.NamedEntityGraphs class-level annotation.

The @NamedEntityGraph annotation must be applied on the root of the graph of entities. That is, if the EntityManager.find or query operation has as its root entity the EmailMessage class, the named entity graph used in the operation must be defined in the EmailMessage class:

@NamedEntityGraph
@Entity
public class EmailMessage {
    @Id
    String messageId;
    String subject;
    String body;
    String sender;
}

In this example, the EmailMessage class has a @NamedEntityGraph annotation to define a named entity graph that defaults to the name of the class, EmailMessage. No fields are included in the @NamedEntityGraph annotation as attribute nodes, and the fields are not annotated with metadata to set the fetch type, so the only field that will be eagerly fetched in either a load graph or fetch graph is messageId.

The attributes of a named entity graph are the fields of the entity that should be included in the entity graph. Add the fields to the entity graph by specifying them in the attributeNodes element of @NamedEntityGraph with a jakarta.persistence.NamedAttributeNode annotation:

@NamedEntityGraph(name="emailEntityGraph", attributeNodes={
    @NamedAttributeNode("subject"),
    @NamedAttributeNode("sender")
})
@Entity
public class EmailMessage { ... }

In this example, the name of the named entity graph is emailEntityGraph and includes the subject and sender fields.

Multiple @NamedEntityGraph definitions may be applied to a class by grouping them within a @NamedEntityGraphs annotation.

In the following example, two entity graphs are defined on the EmailMessage class. One is for a preview pane, which fetches only the sender, subject, and body of the message. The other is for a full view of the message, including any message attachments:

@NamedEntityGraphs({
    @NamedEntityGraph(name="previewEmailEntityGraph", attributeNodes={
        @NamedAttributeNode("subject"),
        @NamedAttributeNode("sender"),
        @NamedAttributeNode("body")
    }),
    @NamedEntityGraph(name="fullEmailEntityGraph", attributeNodes={
        @NamedAttributeNode("sender"),
        @NamedAttributeNode("subject"),
        @NamedAttributeNode("body"),
        @NamedAttributeNode("attachments")
    })
})
@Entity
public class EmailMessage { ... }

Obtaining EntityGraph Instances from Named Entity Graphs

Use the EntityManager.getEntityGraph method, passing in the named entity graph name, to obtain EntityGraph instances for a named entity graph:

EntityGraph<EmailMessage> eg = em.getEntityGraph("emailEntityGraph");

Using Entity Graphs in Query Operations

To specify entity graphs for both typed and untyped queries, call the setHint method on the query object and specify either jakarta.persistence.loadgraph or jakarta.persistence.fetchgraph as the property name and an EntityGraph instance as the value:

EntityGraph<EmailMessage> eg = em.getEntityGraph("previewEmailEntityGraph");
List<EmailMessage> messages = em.createNamedQuery("findAllEmailMessages")
        .setParameter("mailbox", "inbox")
        .setHint("jakarta.persistence.loadgraph", eg)
        .getResultList();

In this example, the previewEmailEntityGraph is used for the findAllEmailMessages named query.

Typed queries use the same technique:

EntityGraph<EmailMessage> eg = em.getEntityGraph("previewEmailEntityGraph");

CriteriaQuery<EmailMessage> cq = cb.createQuery(EmailMessage.class);
Root<EmailMessage> message = cq.from(EmailMessage.class);
TypedQuery<EmailMessage> q = em.createQuery(cq);
q.setHint("jakarta.persistence.loadgraph", eg);
List<EmailMessage> messages = q.getResultList();