JavaScript Required

We're sorry, but we doesn't work properly without JavaScript enabled.

EntityGraphs in Hibernate 5

Technology:

entitygraphs-in-hibernate-5Hibernate is one of the leading frameworks used for Database interactions, also it is one of the JPA Specification implementer.

JPA 2.1 introduced Entity Graphs feature as a more advanced way of handling performance issues.

Until JPA2.0, to load an entity association, we need to specify FetchType.LAZY, FetchType.EAGER as fetching strategies, at entity level.But these associations are static meaning once we specify at the entity level, no provision to switch the fetch strategies at runtime.

Using this EntityGraphs, we load the entity associations using any fetch strategies, either we can use static mapping (specified at entity level), or we can decide theassociations to load in parent query or in separate query.

Defining the Entities :

We will create 3 entities, one is a Module, each module will have multiple Projects, and each Java software project will have multiple contractors.

@Entity @Data publicclass Module { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) privateintid; private String name; @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY) private Set<Project> projects = new HashSet<Project> (); publicvoid addProject(Project project) { projects.add(project); } } @Entity @Data publicclass Project { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) privateintid; private String name; @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY) private Set<Contractor> contractors = new HashSet<Contractor> (); publicvoid addContractor(Contractor contractor) { contractors.add(contractor); } } @Entity @Data @NoArgsConstructor publicclass Contractor { @Id @GeneratedValue privatelongid; private String name; private String role; public Contractor(String name, String role) { this.name = name; this.role = role; } }

Module has the lazy loading strategy for projects field, means hibernate will not get the associated projects in a single query.

When we try to load the module using entityManager, it will load only module entity.

Module retunedModule = entityManager.find(Module.class, module.getId()); final PersistenceUtil persistenceUtil = entityManager.getEntityManagerFactory().getPersistenceUnitUtil(); assertFalse(persistenceUtil.isLoaded(retunedModule.getProjects()));

Note that we are mapped projects in Module entity as a lazy fetch type, we cannot switch to fetch on runtime until JPA 2.0

Defining Entity Graph using JPA Annotations:

@NamedEntityGraph: JAP 2.1 introduces this annotation, it is used to specify the list of attributes, want to fetch from tables along with parent entity, ignoring the fetch type defined at the field level.

It will result in one single join query which will load an entity, and specified attributes.

Add the annotation at the class level for Module entity

@NamedEntityGraph(name = "graph.module.projects", attributeNodes = @NamedAttributeNode(value = "projects"))

We will use entity graph name in entity manager fetch methods.

EntityGraph<?>graph = entityManager.getEntityGraph("graph.module.projects"); Map<String, Object> properties = new HashMap<>(); properties.put("javax.persistence.fetchgraph", graph); Module retunedModule = entityManager.find(Module.class, module.getId(), properties); assertTrue(persistenceUtil.isLoaded(retunedModule, "projects"));

It will generate the below joined SQL query:

select module0_.id as id1_11_0_, module0_.name as name2_11_0_, projects1_.Module_id as Module_i1_12_1_, project2_.id as projects2_12_1_, project2_.id as id1_13_2_, project2_.name as name2_13_2_ from Module module0_ left outer join Module_Project projects1_ on module0_.id=projects1_.Module_id left outer join Project project2_ on projects1_.projects_id=project2_.id where module0_.id=?

But the problem with approachis when we want to find the all contractors related module, which leads to N+1 problem.

1 query for getting the project, and for each project one query for getting the contractors information.

To overcome this situation, @NamedAttributeNode provides one more attribute called subgraph, which is used to load elements in same query.

@NamedEntityGraph(name = "graph.module.projects.contracts", attributeNodes = @NamedAttributeNode(value = "projects", subgraph = "projects.contractors"), subgraphs = @NamedSubgraph(name = "projects.contractors", attributeNodes = @NamedAttributeNode("contractors")))

It is the entityGraph is used to load module, projects and also contracts in one query, which will improve the performance of the application.

entityManager = emf.createEntityManager(); graph = entityManager.getEntityGraph("graph.module.projects.contracts"); properties = new HashMap<>(); properties.put("javax.persistence.fetchgraph", graph); retunedModule = entityManager.find(Module.class, module.getId(), properties);

retunedModule object will contain the module properties, along with projects, and each project will contain contractor information.

The generated SQL join query will look like below:

select module0_.id as id1_11_0_, module0_.name as name2_11_0_, projects1_.Module_id as Module_i1_12_1_, project2_.id as projects2_12_1_, project2_.id as id1_13_2_, project2_.name as name2_13_2_, contractor3_.Project_id as Project_1_14_3_, contractor4_.id as contract2_14_3_, contractor4_.id as id1_8_4_, contractor4_.name as name2_8_4_, contractor4_.role as role3_8_4_ from Module module0_ left outer join Module_Project projects1_ on module0_.id=projects1_.Module_id left outer join Project project2_ on projects1_.projects_id=project2_.id left outer join Project_Contractor contractor3_ on project2_.id=contractor3_.Project_id left outer join Contractor contractor4_ on contractor3_.contractors_id=contractor4_.id where module0_.id=?

Defining Entity Graphs using JPA API:

JPA also provides an API for defining the entity graphs

entityManager has createEntityGraph(Class <T> rootType) method used to create entity graph for given entity as root entity.

entityManager = emf.createEntityManager(); final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); final CriteriaQuery<Module> criteriaQuery = criteriaBuilder.createQuery(Module.class); final Root<Module> root = criteriaQuery.from(Module.class); criteriaQuery.where(criteriaBuilder.equal(root.<Long> get("id"), module.getId())); final TypedQuery<Module> typedQuery = entityManager.createQuery(criteriaQuery); final EntityGraph<Module> moduleGraph = entityManager.createEntityGraph(Module.class); moduleGraph.addSubgraph("projects").addAttributeNodes("contractors"); typedQuery.setHint("javax.persistence.fetchgraph", moduleGraph); final Module testModule = typedQuery.getSingleResult();

similar to above procedure, create Map instance, and add the “javax.persistence.fetchgraph” key and value as entity graph name, and pass this properties variable into entity manager find method.

The only difference here, we are creating entity graph on runtime, instead of defining at the static level.

Types of Entity Graphs:

JPA provides 2 ways to load the entity Graphs at runtime.

  • javax.persistence.fetchgraph: Only the specified attributes are retrieved from the database.We can remark that in comparison to the JPA specs, attributes fixed configured as EAGER are also loaded.

  • javax.persistence.loadgraph: In addition to the specified attributes, attributes statically configured as EAGER are also retrieved.

Conclusion:

In this article we learned about entityGraph to dynamically fetch the associated entities. The decision will be made the runtime instead of compiletime to load the associations, and various ways to create entity graphs (using annotations, using JPA API).

 
Ast Note

Some of our clients

team