Over a million developers have joined DZone.

Hibernate Facts: Multi-Level Fetching

It's quite common to retrieve a root entity along with its children associations on multiple levels.

· Performance Zone

Evolve your approach to Application Performance Monitoring by adopting five best practices that are outlined and explored in this e-book, brought to you in partnership with BMC.

It's quite common to retrieve a root entity along with its children associations on multiple levels.

In our example we need to load a Forest with its Trees and Branches and Leaves, and we will try to see how Hibernate behaves for three collection types: Sets, Indexed Lists, and Bags.

This is how our class hierarchy looks like:


Using Sets and Indexed Lists is straight forward since we can load all entities by running the following JPA-QL query:

Forest f = entityManager.createQuery(
"select f " +
"from Forest f " +
"join fetch f.trees t " +
"join fetch t.branches b " +
"join fetch b.leaves l ", Forest.class)
.getSingleResult();

and the executed SQL query is:

SELECT forest0_.id        AS id1_7_0_,
       trees1_.id         AS id1_18_1_,
       branches2_.id      AS id1_4_2_,
       leaves3_.id        AS id1_10_3_,
       trees1_.forest_fk  AS forest_f3_18_1_,
       trees1_.index      AS index2_18_1_,
       trees1_.forest_fk  AS forest_f3_7_0__,
       trees1_.id         AS id1_18_0__,
       trees1_.index      AS index2_0__,
       branches2_.index   AS index2_4_2_,
       branches2_.tree_fk AS tree_fk3_4_2_,
       branches2_.tree_fk AS tree_fk3_18_1__,
       branches2_.id      AS id1_4_1__,
       branches2_.index   AS index2_1__,
       leaves3_.branch_fk AS branch_f3_10_3_,
       leaves3_.index     AS index2_10_3_,
       leaves3_.branch_fk AS branch_f3_4_2__,
       leaves3_.id        AS id1_10_2__,
       leaves3_.index     AS index2_2__
FROM   forest forest0_
INNER JOIN tree trees1_ ON forest0_.id = trees1_.forest_fk
INNER JOIN branch branches2_ ON trees1_.id = branches2_.tree_fk
INNER JOIN leaf leaves3_ ON branches2_.id = leaves3_.branch_fk

But when our children associations are mapped as Bags, the same JPS-QL query throws a "org.hibernate.loader.MultipleBagFetchException".

In case you can't alter your mappings (replacing the Bags with Sets or Indexed Lists) you might be tempted to try the something like:

BagForest forest = entityManager.find(BagForest.class, forestId);
for (BagTree tree : forest.getTrees()) {
for (BagBranch branch : tree.getBranches()) {
branch.getLeaves().size();
}
}

But this is inefficient generating a plethora of SQL queries:

select trees0_.forest_id as forest_i3_1_1_, trees0_.id as id1_3_1_, trees0_.id as id1_3_0_, trees0_.forest_id as forest_i3_3_0_, trees0_.index as index2_3_0_ from BagTree trees0_ where trees0_.forest_id=?               
select branches0_.tree_id as tree_id3_3_1_, branches0_.id as id1_0_1_, branches0_.id as id1_0_0_, branches0_.index as index2_0_0_, branches0_.tree_id as tree_id3_0_0_ from BagBranch branches0_ where branches0_.tree_id=?
select leaves0_.branch_id as branch_i3_0_1_, leaves0_.id as id1_2_1_, leaves0_.id as id1_2_0_, leaves0_.branch_id as branch_i3_2_0_, leaves0_.index as index2_2_0_ from BagLeaf leaves0_ where leaves0_.branch_id=?        
select leaves0_.branch_id as branch_i3_0_1_, leaves0_.id as id1_2_1_, leaves0_.id as id1_2_0_, leaves0_.branch_id as branch_i3_2_0_, leaves0_.index as index2_2_0_ from BagLeaf leaves0_ where leaves0_.branch_id=?        
select branches0_.tree_id as tree_id3_3_1_, branches0_.id as id1_0_1_, branches0_.id as id1_0_0_, branches0_.index as index2_0_0_, branches0_.tree_id as tree_id3_0_0_ from BagBranch branches0_ where branches0_.tree_id=?
select leaves0_.branch_id as branch_i3_0_1_, leaves0_.id as id1_2_1_, leaves0_.id as id1_2_0_, leaves0_.branch_id as branch_i3_2_0_, leaves0_.index as index2_2_0_ from BagLeaf leaves0_ where leaves0_.branch_id=?        
select leaves0_.branch_id as branch_i3_0_1_, leaves0_.id as id1_2_1_, leaves0_.id as id1_2_0_, leaves0_.branch_id as branch_i3_2_0_, leaves0_.index as index2_2_0_ from BagLeaf leaves0_ where leaves0_.branch_id=? 

So, my solution is to simply get the lowest level children and fetch all needed associations all the way up the entity hierarchy.

Running this code:

List<BagLeaf> leaves = transactionTemplate.execute(new TransactionCallback<List<BagLeaf>>() {
@Override
public List<BagLeaf> doInTransaction(TransactionStatus transactionStatus) {
List<BagLeaf> leaves = entityManager.createQuery(
"select l " +
"from BagLeaf l " +
"inner join fetch l.branch b " +
"inner join fetch b.tree t " +
"inner join fetch t.forest f " +
"where f.id = :forestId",
BagLeaf.class)
.setParameter("forestId", forestId)
.getResultList();
return leaves;
}
});


generates only one SQL query:

 SELECT bagleaf0_.id        AS id1_2_0_,
       bagbranch1_.id      AS id1_0_1_,
       bagtree2_.id        AS id1_3_2_,
       bagforest3_.id      AS id1_1_3_,
       bagleaf0_.branch_id AS branch_i3_2_0_,
       bagleaf0_.index     AS index2_2_0_,
       bagbranch1_.index   AS index2_0_1_,
       bagbranch1_.tree_id AS tree_id3_0_1_,
       bagtree2_.forest_id AS forest_i3_3_2_,
       bagtree2_.index     AS index2_3_2_
FROM   bagleaf bagleaf0_
       INNER JOIN bagbranch bagbranch1_
               ON bagleaf0_.branch_id = bagbranch1_.id
       INNER JOIN bagtree bagtree2_
               ON bagbranch1_.tree_id = bagtree2_.id
       INNER JOIN bagforest bagforest3_
               ON bagtree2_.forest_id = bagforest3_.id
WHERE  bagforest3_.id = ? 

We get a List of Leaf objects, but each Leaf fetched also the Branch,which fetched the Tree and then the Forest too. Unfortunately Hibernate can't magically create the up-down hierarchy from a query result like this.

Trying to access the bags with:

leaves.get(0).getBranch().getTree().getForest().getTrees();

simply throws a LazyInitializationException, since we are trying to access an uninitialized lazy proxy list, outside of an opened Persistence Context.

So, we just need to recreate the Forest hierarchy ourselves from the List of Leaf objects.

And this is how I did it:

EntityGraphBuilder entityGraphBuilder = new EntityGraphBuilder(new EntityVisitor[] {
BagLeaf.ENTITY_VISITOR, BagBranch.ENTITY_VISITOR, BagTree.ENTITY_VISITOR, BagForest.ENTITY_VISITOR
}).build(leaves);
ClassId<BagForest> forestClassId = new ClassId<BagForest>(BagForest.class, forestId);
BagForest forest = entityGraphBuilder.getEntityContext().getObject(forestClassId);


The EntityGraphBuilder is one utility I wrote that takes an array of EntityVisitor objects and applies them against the visited objects. This goes recursively up to the Forest object, and we are replacing the Hibernate collections with new ones, adding each child to the parent children collection.

More on my original post.

Learn tips and best practices for optimizing your capacity management strategy with the Market Guide for Capacity Management, brought to you in partnership with BMC.

Topics:
java ,high-perf ,hibernate ,theory ,performance

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}