About Vlad Mihalcea

Vlad Mihalcea is a software architect passionate about software integration, high scalability and concurrency challenges.

Hibernate Facts: Multi level fetching

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 have Hibernate behaves for three collection types: Sets, Indexed Lists, and Bags.

This is how our class hierarchy looks like:
 
 
 
 
multilevel

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.

Since the children collections were replaced, it’s safer not to reattach/merge this object in a new Persistence Context, as all Bags will be marked as dirty.

This is how the Entity uses its visitors:

private <T extends Identifiable, P extends Identifiable> void visit(T object) {
	Class<T> clazz = (Class<T>) object.getClass();
	EntityVisitor<T, P> entityVisitor = visitorsMap.get(clazz);
	if (entityVisitor == null) {
		throw new IllegalArgumentException("Class " + clazz + " has no entityVisitor!");
	}
	entityVisitor.visit(object, entityContext);
	P parent = entityVisitor.getParent(object);
	if (parent != null) {
		visit(parent);
	}
}

And the base EntityVisitor looks like this:

public void visit(T object, EntityContext entityContext) {
	Class<T> clazz = (Class<T>) object.getClass();
	ClassId<T> objectClassId = new ClassId<T>(clazz, object.getId());
	boolean objectVisited = entityContext.isVisited(objectClassId);
	if (!objectVisited) {
		entityContext.visit(objectClassId, object);
	}
	P parent = getParent(object);
	if (parent != null) {
		Class<P> parentClass = (Class<P>) parent.getClass();
		ClassId<P> parentClassId = new ClassId<P>(parentClass, parent.getId());
		if (!entityContext.isVisited(parentClassId)) {
			setChildren(parent);
		}
		List<T> children = getChildren(parent);
		if (!objectVisited) {
			children.add(object);
		}
	}
}

This code is packed as a utility, and the customization comes through extending the EntityVisitors like this:

public static EntityVisitor<BagForest, Identifiable> ENTITY_VISITOR = new EntityVisitor<BagForest, Identifiable>(BagForest.class) {};

public static EntityVisitor<BagTree, BagForest> ENTITY_VISITOR = new EntityVisitor<BagTree, BagForest>(BagTree.class) {
	public BagForest getParent(BagTree visitingObject) {
		return visitingObject.getForest();
	}

	public List<BagTree> getChildren(BagForest parent) {
		return parent.getTrees();
	}

	public void setChildren(BagForest parent) {
		parent.setTrees(new ArrayList<BagTree>());
	}
};

public static EntityVisitor<BagBranch, BagTree> ENTITY_VISITOR = new EntityVisitor<BagBranch, BagTree>(BagBranch.class) {
	public BagTree getParent(BagBranch visitingObject) {
		return visitingObject.getTree();
	}

	public List<BagBranch> getChildren(BagTree parent) {
		return parent.getBranches();
	}

	public void setChildren(BagTree parent) {
		parent.setBranches(new ArrayList<BagBranch>());
	}
};

public static EntityVisitor<BagLeaf, BagBranch> ENTITY_VISITOR = new EntityVisitor<BagLeaf, BagBranch>(BagLeaf.class) {
	public BagBranch getParent(BagLeaf visitingObject) {
		return visitingObject.getBranch();
	}

	public List<BagLeaf> getChildren(BagBranch parent) {
		return parent.getLeaves();
	}

	public void setChildren(BagBranch parent) {
		parent.setLeaves(new ArrayList<BagLeaf>());
	}
};

This is not the Visitor pattern “per se”, but it has a slight resemblance with it. Although it’s always better to simply use indexed Lists or Sets, you can still get your graph of associations using a single query for Bags too.

 

Reference: Hibernate Facts: Multi level fetching from our JCG partner Vlad Mihalcea at the Vlad Mihalcea’s Blog blog.
Related Whitepaper:

Functional Programming in Java: Harnessing the Power of Java 8 Lambda Expressions

Get ready to program in a whole new way!

Functional Programming in Java will help you quickly get on top of the new, essential Java 8 language features and the functional style that will change and improve your code. This short, targeted book will help you make the paradigm shift from the old imperative way to a less error-prone, more elegant, and concise coding style that’s also a breeze to parallelize. You’ll explore the syntax and semantics of lambda expressions, method and constructor references, and functional interfaces. You’ll design and write applications better using the new standards in Java 8 and the JDK.

Get it Now!  

Leave a Reply


five × = 40



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.

Sign up for our Newsletter

15,153 insiders are already enjoying weekly updates and complimentary whitepapers! Join them now to gain exclusive access to the latest news in the Java world, as well as insights about Android, Scala, Groovy and other related technologies.

As an extra bonus, by joining you will get our brand new e-books, published by Java Code Geeks and their JCG partners for your reading pleasure! Enter your info and stay on top of things,

  • Fresh trends
  • Cases and examples
  • Research and insights
  • Two complimentary e-books