Entity graphs

JPA fundamentally permits field and properties for persistence capable objects, mapped superclasses and embedded classes, to be retrieved as FetchType.EAGER or FetchType.LAZY. Just for the purposes of revision we shall note that FetchType.LAZY is the default for @OneToMany and @ManyToMany associations; FetchType.EAGER is the default for @OneToOne and @ManyToOne associations.

An entity graph is a template that is defined in the form of metadata or an object created by the dynamic Entity Graph API, which captures the path and boundaries for a query or operation. Another way of stating the definition is that an entity graph is an application configurable fetch plan that instructs the JPA provider on how to retrieve entities from the database server.

Note

In computer science terms, a graph is a collection of nodes connected by edges. Nodes are JPA entities and edges are the relationships between entities. A sub graph is a smaller representation of connected nodes derived from a graph. A sub graph is a set of nodes and partial edges taken from the master graph.

JPA 2.1 introduces the notion of an entity graphs object, which can be an annotation or programmatically created. Entity graphs can only be applied to a JPA query or entity manager find() operations.

There are three low-level classes to be aware of: EntityGraph, AttributeNode, and Subgraph, which are found in the package javax.persistence.

Interface

Description

Subgraph<T>

Represents a sub graph of entities that are managed by association. A sub-graph can contain further attributes and sub-graphs.

EntityGraph<T>

Holds the meta model of the fetch plan for an entity and the attributes that will be retrieved eagerly, which can include sub-graphs of further dependent entities.

AttributeNode<T>

Represents the attribute of the entity graph, which may also hold the map collection of sub-graph of entities.

An entity graph, however, can be defined with annotations: @NamedEntityGraph, @NamedAttributeNode, and @NamedSubgraph. These correspond to the Java interfaces.

The following diagram illustrates the relationship between a graph and a subgraph:

Entity graphs

A graph and a related sub-graph

Here are the key attributes for @NamedEntityGraph:

Attribute name

Type

Description

Default

name

String

Specifies the name of the entity graph

Empty

attributeNodes

NamedAttribute-Node []

Specifies an optional list of attributes that will be eagerly fetched as part of this plan

Empty

includeAllAttributes

boolean

If true then includes all of the attributes of the entity class without explicitly listing them

false

subgraphs

NamedSubgraph[]

A list of sub-graphs included in this fetch plan

Empty

Here are the key attributes for @NamedEntityGraph:

Attribute name

Type

Description

Default

value

String

Specifies the name of the attribute that is eagerly fetched

Required

subgraph

String

A reference to another managed type (sub-graph) which has its own fetch plan definition

Empty

keySubgraph

String

For sub-graphs that reference an association java.util.Map type, this attribute configures the key into a collection

Empty

Here are the key attributes for @NamedSubgraph:

Attribute name

Type

Description

Default

name

String

Specifies the name of the sub-graph

Required

type

Class

The sub-graph type

void.class

attributeNodes

NamedAttribute-Node[]

Attributes of the sub-graph

Empty

Worked example of a fetch plan

We shall revisit the recording music business as our domain. The important entities, as we have already seen, are the artist, concert event, and album. Suppose we throw another domain object such as legally binding contracts into this mix. If every entity was fetched eagerly then we could face the possibility of retrieving row upon row from joined database tables in our little database. This would probably not be great for efficiency in our web application, so I think that calls for a fetch plan!

Let's present our dependent entities first starting with the refactoring of the artist's music event:

@Entity
@Table(name = "CONCERT_EVENT")
public class ConcertEvent {
    @Id @Column(name="CONCERT_EVENT_ID")
    protected long id;
    @Column(nullable=false, name="ARTIST_NAME")
    protected String name;
    @OneToOne(cascade = CascadeType.ALL)
    protectedEventTypeeventType;
 
    @ManyToOne(cascade = CascadeType.ALL)
    protected Artist artist;
 
    @OneToMany(cascade=CascadeType.ALL, 
           fetch = FetchType.LAZY )
    protected List<Contract> contracts = new ArrayList<>();

    public ConcertEvent() {} /* ... */
}

The ConcertEvent entity is the top class in a hierarchy of different recording events including theatre, revenue, and charity. This entity has a primary key id and a basic name field. It has a many-to-one relationship to the artist. We add a new relation; a concert can have many legal contracts. We explicitly hint to the JPA Provider to retrieve contracts lazily with FetchType.LAZY.

For now, we only require one subclass of the concert event, namely:

@Entity
@Table(name="LIVE_EVENT")
@Inheritance
public class LiveEvent extends ConcertEvent{
    @Size(min=3) protected String stadium;
    @Min(10) protected int capacity;

    publicLiveEvent() {} /* ... */
}

The LiveEvent entity is a direct sub class of ConcertEvent; note that we are using the default mode of InheritanceType.SINGLE_TABLE. A live event, we assume, takes place at a stadium and has a maximum capacity. Essentially, we refactored this previous version of the LiveEvent entity by pulling up the members into the new super class. It doesn't matter about the accuracy and precision of the domain model.

Let's define the Contract entity to associate with ConcertEvent:

@Entity
public class Contract {
    @Id @Column(name="CONTRACT_ID")
    private long id;
    private String title;
    @Lob private String document;
    
    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(nullable = false)
    privateConcertEventconcertEvent;

    public Contract() {}  /* ... */
}

The field document is annotated with @Lob to denote it as capable of storing long-binary objects. Since document is a String, then the database type is a CLOB for storing character data.

We will create two fetch plans for retrieving the Artist and its dependent entities. There are two situations; sometimes it is useful to retrieve just the essential basic properties of artist with the event dependency and sometimes it is useful to retrieve the Artist and ConcertEvent entities.

Here is a definition of Artist with the entity graph as annotations:

@NamedEntityGraphs(value = {
    @NamedEntityGraph(
        name="Artist.NoConcerts",
        attributeNodes={ 
            @NamedAttributeNode("name") }
    ),
    @NamedEntityGraph(
        name="Artist.WithConcerts",
        attributeNodes={
            @NamedAttributeNode("name"),
            @NamedAttributeNode("events") }
    ),
})
@Entity
public class Artist {
    @Id @Column(name="ARTIST_ID")
    private long id;
    @Column(nullable=false, name="ARTIST_NAME")
    private String name; 
    @OneToMany(mappedBy="artist",cascade = CascadeType.ALL)
    @MapKeyJoinColumn(name="EVENT_TYPE_ID")
    private Map<EventType, ConcertEvent> events = new HashMap<>();

    public Artist() {}   /* ... */
}

The entity Artist is annotated with @NamedEntityGraphs. We declare two @NamedEntityGraph annotations in an array. Each entity graph represents a fetch plan for the JPA provider and must have a unique name across the persistence unit. The fetch plan is attached to the entity that is being annotated and its dependent object if a sub-graph is used. Each entity graph has an option set of @NamedAttributeNode that identifies the field or properties of the entity that should be eagerly loaded by the JPA provider.

In summary, the named entity graph Artist.NoConcert loads the artist without retrieving the concert information. or in other words, the map collection of ConcertEvent elements is lazily loaded. The named entity graph Artist.WithConcerts informs the JPA provider to load the Artist and the collection of ConvertEvent entities.

Fetch plans, therefore, allow the application to control the performance of querying deeply dependent objects. Loading the concert data eagerly also entails loading the sub types, which could also have other eagerly loaded dependent entities. However, because the Contract association is FetchType.LAZY then it is not loaded by the first query with the Artist.WithConcerts fetch plan. Here I am using a fetch plan and entity graph synonymously.

As usual, let's look at an Arquillian test that demonstrates the entity graph feature. Here is the cut-down code for the class ArtistEntityGraphRetrievalTest:

@RunWith(Arquillian.class)
public class ArtistEntityGraphRetrievalTest {
    // ... code omitted 
    @PersistenceContextEntityManager em;
    @Resource UserTransaction utx;

    @Test @InSequence(1)
    public void shouldSaveArtistWithAlbum() throws Exception{
    StringBuilder text = new StringBuilder();
    for ( int j=0; j<256; ++j) {
        text.append((char)(65 + Math.random() * 26)); }
        Contract contract =
            new Contract( 5150, "M and Ms", text.toString());
        EventType eventType = 
            New EventType(808, "WORLD TOUR");
        Artist artist = new Artist(1002, "Lady Gaga" );
        LiveEvent event = new LiveEvent(97502, 
            "The Monster Ball Tour",
            eventType, artist, "Tokyo Dome", 55000 );
        event.getContracts().add(contract);
        contract.setConcertEvent(event);
        artist.getEvents().put( eventType, event );

        utx.begin();
        em.persist(artist);
        utx.commit();

        Artist art2 = em.find(Artist.class, artist.getId());
        Utils.assertEqualMaps(
        art2.getEvents(), art2.getEvents());
    }

    // ...
}

We create sample data with a recording artist and the dependent entities in the first test method, shouldSaveArtistWithAlbum(). We take full advantage of Arquillian's ability to execute test methods in a defined order with the annotation @InSequence, which takes a single value to indicate the execution order; higher numbers are lower priority. It allows us to write a test method that executes before the others for the purpose of populating the database. This test, of course, runs against a real database. I used MySQL.

We construct the Construct entity with a document that contains 256 random characters. Imagine if this entity had a large dataset; retrieving every document for each row in the database, indeed, would be suboptimal-I think you get the picture. The remaining part of set up, builds the entities and connects them together. We must ensure the relationships are correctly defined on the Java side for bidirectional associations so that correspond to the entity relationships between the physical database tables (or views).

Let's verify the operation of the first fetch graph with a test method:

private Artist getArtistWithEntityGraph( String entityGraph) {
  EntityGraph artistGraph = em.getEntityGraph(entityGraph);
  return (Artist) em.createQuery("Select a from Artist a")
     .setHint("javax.persistence.fetchgraph", artistGraph)
     .getResultList()
     .get(0);
}

@Test  @InSequence(2)
public void shouldLoadArtistWithoutConcerts() throws Exception{
  Artist artist = getArtistWithEntityGraph(
      "Artist.NoConcerts");
  PersistenceUnitUtil util =
      em.getEntityManagerFactory()
          .getPersistenceUnitUtil();
  assertTrue(util.isLoaded(artist, "id"));
  assertTrue(util.isLoaded(artist, "name"));
  assertFalse(util.isLoaded(artist, "events"));
}

The method getArtistWithEntityGraph() is a helper function to set the fetch plan for the current entity manager. We ask the entity manager for a named EntityGraph instance. At the same time, when we make a JPA query, we set a hint on the Query instance with the javax.persistence.fetchgraph key special and the EntityGraph value. The method chaining on the query makes this ever so convenient; we execute it and retrieve a single result, which is the artist from the earlier test method shouldSaveArtistWithAlbum().

The test method shouldLoadArtistWithoutConcerts() is also annotated with @InSequence with a higher value. We make use of the refactored method getArtistWithEntityGraph() in order to retrieve the Artist entity with a known fetch plan. We retrieve the internal fetch plan of the entity manager from the factory instance. There is a special utility class PersistenceUnitUtil, which has some useful methods. The isLoaded() method checks if the entity has an attribute fetched by the persistence context. The attribute is available to the application for use, if this method returns true.

The entity graph Artist.NoConcerts applied to a query hints to the JPA provider to load the artist and retrieve the id and name, but the events collection is not fetched. This is the purpose of the test.

Let's look at the third test shouldLoadArtistWithConcerts(), which should be straightforward to understand now:

@Test  @InSequence(3)
public void shouldLoadArtistWithConcerts() throws Exception{
  PersistenceUnitUtil util =
      em.getEntityManagerFactory().getPersistenceUnitUtil();
  Artist artist = 
      getArtistWithEntityGraph("Artist.WithConcerts");
  assertTrue(util.isLoaded(artist, "id"));
  assertTrue(util.isLoaded(artist, "name"));
  assertTrue(util.isLoaded(artist, "events"));
}

The only difference between this test and the last one is the named entity graph and the assertion. We shall do something very useful with sub-graphs or dependent collection entities associated with the root entity.

Worked example of a fetch plan

The output of running entity graph Arquillian test

Let's expand our set of fetch plans, which are annotated to the Artist entity, by one more method, @NamedEntityGraph.

@NamedEntityGraphs(value = {
    /* ... see previous ... */ ,
    @NamedEntityGraph(
        name="Artist.WithConcertsAndContracts",
        attributeNodes={
            @NamedAttributeNode("name"),
            @NamedAttributeNode(
                value = "events",
                subgraph = "specialEvents"),
        },
        subgraphs = {
            @NamedSubgraph(
                name="specialEvents",
                attributeNodes={
                    @NamedAttributeNode("name"),
                    @NamedAttributeNode("contracts"),
                }
            ),
        }
    ), 
})

Here in this entity graph, named Artist.WithConcertsAndContracts, we want to eagerly retrieve the concert events and also the contracts. If you remember, the contracts field was annotated as FetchType.LAZY, so we strongly hint the JPA provider to override the default fetch plan to this association between a ConcertType and Contract entities. In the fetch plan for the Artist entity, we associate a named sub-graph called specialEvents with the named attribute node, events. This ensures that the JPA provider eagerly fetches the ConcertType map collection.

We define a @NameSubgraph called specialEvents, which configures the eager loading of node attributes in the ConcertType entity. The named sub-graph eagerly retrieves the name field and the contracts association.

Let's prove this fetch plan with a final Arquillian unit test.

@Test  @InSequence(5)
public void shouldLoadArtistWithLiveConcertsAndContracts()
  throws Exception{
  PersistenceUnitUtilutil =
    em.getEntityManagerFactory().getPersistenceUnitUtil();
  Artist artist = getArtistWithEntityGraph(
    "Artist.WithConcertsAndContracts");
  ConcertEvent event = artist.getEvents()
    .values().iterator().next();
  assertTrue(util.isLoaded(event, "id"));
  assertTrue(util.isLoaded(event, "name"));
  assertTrue(util.isLoaded(event, "eventType"));
  assertTrue(util.isLoaded(event, "contracts"));
}

In the test method shouldLoadArtistWithLiveConcertsAndContracts(), we retrieve the recording artist with the entity graph Artist.WithConcertsAndContracts. This time around, we retrieve the first concert event from the collection, because we know it should be there. With the ConcertEvent instance, we test that specific named attributes have been loaded. We expect the contracts map collection to have been fetched.

Tip

Entity graph attribute versus the default fetch plan

Does the entity or sub graph attribute override an explicitly coded lazy attribute? Yes, mappings for attributes of the related entity can be overridden through a sub-graph. If the attribute node is not supplied in the entity graph, then the default or explicit setting for that attribute takes effect.

Fetch graphs are useful for merging an entity in the detached state with an incoming persistence context. This concludes the subsection on the entity graphs.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.202.27