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.