Generic Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Overview

Simply put, Entity Graphs are another way to describe a query in JPA 2.1. We can use them to formulate better-performing queries.

In this tutorial, we're going to learn how to implement Entity Graphs with Spring Data JPA through a simple example.

2. The Entities

First, let's create a model called Item which has multiple characteristics:

@Entity
public class Item {

    @Id
    private Long id;
    private String name;
    
    @OneToMany(mappedBy = "item")
    private List<Characteristic> characteristics = new ArrayList<>();

    // getters and setters
}

Now let's define the Characteristic entity:

@Entity
public class Characteristic {

    @Id
    private Long id;
    private String type;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn
    private Item item;

    //Getters and Setters
}

As we can see in the code, both the characteristics field in the Item entity and the item field in the Characteristic entity are loaded lazily using the fetch parameter. So, our goal here is to load them eagerly at runtime.

3. The Entity Graphs

In Spring Data JPA, we can define an entity graph using a combination of @NamedEntityGraph and @EntityGraph annotations. Or, we can also define ad-hoc entity graphs with just the attributePaths argument of the @EntityGraph annotation.

Let's see how it can be done.

3.1. With @NamedEntityGraph

First, we can use JPA's @NamedEntityGraph annotation directly on our Item entity:

@Entity
@NamedEntityGraph(name = "Item.characteristics",
    attributeNodes = @NamedAttributeNode("characteristics")
)
public class Item {
	//...
}

And then, we can attach the @EntityGraph annotation to one of our repository methods:

public interface ItemRepository extends JpaRepository<Item, Long> {

    @EntityGraph(value = "Item.characteristics")
    Item findByName(String name);
}

As the code shows, we've passed the name of the entity graph, which we've created earlier on the Item entity, to the @EntityGraph annotation. When we call the method, that's the query Spring Data will use.

The default value of the type argument of the @EntityGraph annotation is EntityGraphType.FETCH. When we use this, the Spring Data module will apply the FetchType.EAGER strategy on the specified attribute nodes. And for others, the FetchType.LAZY strategy will be applied.

So in our case, the characteristics property will be loaded eagerly, even though the default fetch strategy of the @OneToMany annotation is lazy.

One catch here is that if the defined fetch strategy is EAGER, then we cannot change its behavior to LAZY. This is by design since the subsequent operations may need the eagerly fetched data at a later point during the execution.

3.2. Without @NamedEntityGraph

Or, we can define an ad-hoc entity graph, too, with attributePaths.

Let's add an ad-hoc entity graph to our CharacteristicsRepository that eagerly loads its Item parent:

public interface CharacteristicsRepository 
  extends JpaRepository<Characteristic, Long> {
    
    @EntityGraph(attributePaths = {"item"})
    Characteristic findByType(String type);    
}

This will load the item property of the Characteristic entity eagerly, even though our entity declares a lazy-loading strategy for this property.

This is handy since we can define the entity graph inline instead of referring to an existing named entity graph.

4. Test Case

Now that we've defined our entity graphs let's create a test case to verify it:

@DataJpaTest
@RunWith(SpringRunner.class)
@Sql(scripts = "/entitygraph-data.sql")
public class EntityGraphIntegrationTest {
   
    @Autowired
    private ItemRepository itemRepo;
    
    @Autowired
    private CharacteristicsRepository characteristicsRepo;
    
    @Test
    public void givenEntityGraph_whenCalled_shouldRetrunDefinedFields() {
        Item item = itemRepo.findByName("Table");
        assertThat(item.getId()).isEqualTo(1L);
    }
    
    @Test
    public void givenAdhocEntityGraph_whenCalled_shouldRetrunDefinedFields() {
        Characteristic characteristic = characteristicsRepo.findByType("Rigid");
        assertThat(characteristic.getId()).isEqualTo(1L);
    }
}

The first test will use the entity graph defined using the @NamedEntityGraph annotation.

Let's see the SQL generated by Hibernate:

select 
    item0_.id as id1_10_0_,
    characteri1_.id as id1_4_1_,
    item0_.name as name2_10_0_,
    characteri1_.item_id as item_id3_4_1_,
    characteri1_.type as type2_4_1_,
    characteri1_.item_id as item_id3_4_0__,
    characteri1_.id as id1_4_0__
from 
    item item0_ 
left outer join 
    characteristic characteri1_ 
on 
    item0_.id=characteri1_.item_id 
where 
    item0_.name=?

For comparison, let's remove the @EntityGraph annotation from the repository and inspect the query:

select 
    item0_.id as id1_10_,
    item0_.name as name2_10_ 
from 
    item item0_ 
where 
    item0_.name=?

From these queries, we can clearly observe that the query generated without @EntityGraph annotation is not loading any properties of Characteristic entity. As a result, it loads only the Item entity.

Lastly, let's compare the Hibernate queries of the second test with the @EntityGraph annotation:

select 
    characteri0_.id as id1_4_0_,
    item1_.id as id1_10_1_,
    characteri0_.item_id as item_id3_4_0_,
    characteri0_.type as type2_4_0_,
    item1_.name as name2_10_1_ 
from 
    characteristic characteri0_ 
left outer join 
    item item1_ 
on 
    characteri0_.item_id=item1_.id 
where 
    characteri0_.type=?

And the query without the @EntityGraph annotation:

select 
    characteri0_.id as id1_4_,
    characteri0_.item_id as item_id3_4_,
    characteri0_.type as type2_4_ 
from 
    characteristic characteri0_ 
where 
    characteri0_.type=?

5. Conclusion

In this tutorial, we've learned how to use JPA Entity Graphs in Spring Data. With Spring Data, we can create multiple repository methods that are linked to different entity graphs.

The examples for this article are available over on GitHub.

Generic bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
Comments are closed on this article!