JPA 2.1 has introduced the Entity Graph feature. Applying the Entity graphs fetches the mapped entities eagerly while querying the database. We can use Entity graphs to eliminate the N+1 select query problem.

In this article, we will learn how to create entity graphs and entity subgraphs with the help annotation and criteria API.

Spring boot JPA entity graphs

Entity graph fetch types

Entity graphs are used to eagerly fetch the mapped entities when applied. The entity graph provides two fetch types. They are fetchGraph and loadGraph.

  • FetchGraph: Applying this fetch type results in fetching all the entity graph attribute nodes eagerly. Other mapped entities will be lazily fetched irrespective of the specified fetch type.
  • LoadGraph: Applying this fetch type results in fetching all the entity graph attribute nodes eagerly. Other mapped entities are treated based on the default/specified fetch type.

Based on the requirement, we can use any of the fetch types.

Creating the Entity Graphs

We can create entity graphs with the help of annotations or dynamically with the help of the criteria API.

Using annotations

We can add annotations to the entity class to specify the entity graph. We can use subgraphs to apply the entity graph to multiple levels.

Let us create an entity graph with the help of annotations.

Create an entity class called School.java

@NamedEntityGraphs(
	value = {
		@NamedEntityGraph(
			name = "school-graph", 
			attributeNodes = { 
				@NamedAttributeNode(value = "students") 
			}
		) 
	}
)
@Entity
@Table(name = "SCHOOL")
@Getter
@Setter
public class School {

	@Id
	@Column(name = "ID")
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Integer id;

	@Column(name = "SCHOOL_NAME")
	private String schoolName;

	@OneToMany(mappedBy = "school", orphanRemoval = true, cascade = CascadeType.ALL)
	private List<Student> students;
}
  • @NamedEntityGraphs: If we are defining multiple entity graphs to an entity class, we can use this annotation to club them together.
  • @NamedEntityGraph: This is the annotation that adds an entity graph for a given entity class. We can use the name attribute to name the entity graph and attributeNodes property to specify all the attribute nodes that are part of the defined entity graph.
  • @NamedAttributeNode: This annotation adds the attribute node to the entity graph. In the above example, we have added an attribute node called students to the entity graph school-graph.

Create a Student.java class. This class will have many to one association with the School entity.

@Getter
@Setter
@Entity
@Table(name = "STUDENT")
public class Student {

	@Id
	@Column(name = "ID")
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Integer id;

	@Column(name = "STUDENT_NAME")
	private String studentName;

	@ManyToOne
	@JoinColumn(name="SCHOOL_ID")
	private School school;
}

Now we have created the required Entity graph for the School.Java entity class. We can fetch all the related entities along with it.

Create a SchoolRepository.java interface and override the spring JPA findAll() method.

public interface SchoolRepository extends JpaRepository<School, Integer> {

	@EntityGraph(type=EntityGraphType.FETCH, value="school-graph")
	public List<School> findAll();
}
  • @EntityGrpah: This annotation applies the entity graph while fetching the entities.
  • type: This attribute specifies the fetch graph type(FETCH or LOAD).
  • value: Name of the entity graph to be applied.

If we examine the generated SQL statement, we can observe that JPA is fetching all the related entities along with the parent entity even though it has default LAZY fetch type.

select 
school0_.id as id1_0_0_, 
students1_.school_id as school_i2_1_1_, 
school0_.school_name as school_n2_0_0_, 
students1_.student_name as student_1_1_1_, 
students1_.school_id as school_i2_1_0__ 
from school school0_ 
left outer join student students1_ 
on school0_.id=students1_.school_id

We can apply the entity graph to the Spring JPA finder methods.

@EntityGraph(type=EntityGraphType.FETCH, value="school-graph")
public List<School> findBySchoolName(String name);

We can also apply the entity graph to the custom query methods.

@EntityGraph(type=EntityGraphType.FETCH, value="school-graph")
@Query(value = "select s from School s where s.schoolName =:name")
public List<School> findSchool(String name);

Both of the above generates the below SQL statement when executed.

select 
school0_.id as id1_0_0_, 
students1_.school_id as school_i2_1_1_, 
school0_.school_name as school_n2_0_0_, 
students1_.student_name as student_1_1_1_, 
students1_.school_id as school_i2_1_0__ 
from school school0_ 
left outer join student students1_ 
on school0_.id=students1_.school_id 
where school0_.school_name=?

Dynamically creating Entity graphs

We can dynamically apply the available entity graphs using the JPA criteria API.

EntityGraph<?> entityGraph = entityManager.getEntityGraph("school-graph");

CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<School> query = builder.createQuery(School.class);
Root<School> schoolRoot = query.from(School.class);
query.select(schoolRoot);
query.where(builder.equal(schoolRoot.get("schoolName"), name));

TypedQuery<School> schoolQuery = entityManager.createQuery(query);
schoolQuery.setHint("javax.persistence.fetchgraph", entityGraph);

List<School> schools = schoolQuery.getResultList();
  • We can get the defined entity graph using the entity manager’s getEntityGraph() method. We can pass the entity graph name as a method parameter.
  • We can use the setHint() method to set the entity graph while fetching the entities using the JPA criteria API.

We can also create the entity graph dynamically using EntityManager, as shown below.

EntityGraph<?> entityGraph = entityManager.createEntityGraph("school-graph");
entityGraph.addAttributeNodes("students");
  • EntityMnager’s createEntityGraph() method creates a new entity graph dynamically.
  • We can add attribute nodes using the addAttributeNodes() method of EntityGraph instance.

Entity sub graphs

Subgraphs allow us to build complex entity graphs with multiple levels. A subgraph is an entity graph, that is part of another entity graph or entity subgraph.

Create an Address.Java class. A student entity class can have an address entity associated with it.

@Getter
@Setter
@Entity
@Table(name = "ADDRESS")
public class Address {

	@Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
	private Integer id;
	
	@Column(name = "NAME")
	private String addressString;
}

Update the Student entity class by adding the address filed.

@Getter
@Setter
@Entity
@Table(name = "STUDENT")
public class Student {

	@Id
	@Column(name = "ID")
	private Integer id;

	@Column(name = "STUDENT_NAME")
	private String studentName;

	@OneToOne(fetch = FetchType.LAZY)
	@MapsId
	private Address address;

	@ManyToOne
	@JoinColumn(name="SCHOOL_ID")
	private School school;
}

Using annotation to define address sub graph

Now add the subgraph to the School entity class, as shown below.

@NamedEntityGraphs(
	value = { 
		@NamedEntityGraph(name = "school-graph", 
			attributeNodes = {
				@NamedAttributeNode(
					value = "students", 
					subgraph = "address-subgraph"
				) 
			}, 
			subgraphs = {
				@NamedSubgraph(
					name = "address-subgraph", 
					attributeNodes = 
					{ 
						@NamedAttributeNode("address") 
					}
				) 
			}
		)
	}
)
@Entity
@Table(name = "SCHOOL")
@Getter
@Setter
@ToString
public class School {
 //school entity attributes.
}
  • We can use the subgraph attribute to specify the subgraph name for a particular entity graph attribute. In the above example, we have added the address-subgraph to the entity graph.
  • We can use the subgraphs attribute of the @NamedEntityGraph annotation to specify the subgraph details.
  • @NamedSubgraph: This annotation specifies the subgraph details. The name attribute is the name of the subgraph.
  • Entity subgraphs can have attribute nodes. We can use @NamedAttributeNode annotation to add the attributes to the subgraph.
  • In the above example, we have added the address attribute node to the address-subgraph subgraph.

Adding sub graph dynamically

EntityGraph<?> entityGraph = entityManager.createEntityGraph("school-graph");
Subgraph<?> subGraph = entityGraph.addSubgraph("students");
subGraph.addAttributeNodes("address");
		
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<School> query = builder.createQuery(School.class);
Root<School> schoolRoot = query.from(School.class);
query.select(schoolRoot);
query.where(builder.equal(schoolRoot.get("schoolName"), name));

TypedQuery<School> schoolQuery = entityManager.createQuery(query);
schoolQuery.setHint("javax.persistence.fetchgraph", entityGraph);

Below SQL query is generated by the JPA, for the above entity graph.

select 
school0_.id as id1_1_0_, 
school0_.school_name as school_n2_1_0_, 
students1_.school_id as school_i2_2_1_,  
students1_.address_id as address_3_2_1_, 
students1_.student_name as student_1_2_1_, 
students1_.school_id as school_i2_2_0__, 
address2_.id as id1_0_2_,
address2_.name as name2_0_2_ 
from school school0_ 
left outer join student students1_ 
on school0_.id=students1_.school_id 
left outer join address address2_ 
on students1_.address_id=address2_.id 
where school0_.school_name=?

Conclusion

In this article, we learned how to create JPA entity graphs and entity subgraphs.

We learned how to use annotations and JPA criteria API to generate the entity graph and subgraphs.

Using entity graphs is an option to eliminate the N+1 select query issue. But This may also result in a Cartesian product number of database record selection that again creates performance issues if the entity graph is applied to fetch multiple related entities.

You may also be interested in