Filed under:
I have recently been busy with loading and caching tree structures with Hibernate, and i had a few struggles with achieving this with a minimal amount of database queries and at the same time caching the entire tree structure. This post will explain how i load and cache a quite plain vanilla tree structure.
For a history on the issues i came across on my way to this "solution", please have a look at the related posts on the Hibernate forum.
First of all, a brief overview of the POJO's and Hibernate mapping files.
Node.java
Node.hbm.xml
ehcache.xml
In order to load the tree structure in as little time as possible, i chose to use an HQL statement which would load all nodes from the database where both the parent property and children collection would be initialised. This was achieved through the following code:
That's it. Whenever i load the tree structure, all Nodes will be fully initialised. Even better, whenever i need to get hands on the tree structure for the second time, the entire tree structure is loaded from the cache. This, of course, is only true until i add, remove or edit nodes in the tree. Whenever i do that, the entire tree needs to be loaded from the database once more.
public class Node { private UUID id = null; private Node parent = null; public UUID getId() {} public void setId(UUID id) {} public Node getParent() {} public void setParent(Node parent) {} }
<hibernate-mapping> <class name="com.tracker.db.entities.Node" table="keywordnode"> <cache usage="read-write"/> <id column="id" length="36" name="id" type="com.tracker.db.usertypes.CustomUUID"/> <version column="updated" name="updated" type="java.util.Calendar" unsaved-value="null"/> <property column="name" length="200" name="name" type="java.lang.String"/> <many-to-one cascade="none" class="com.tracker.db.entities. Node" column="parent_id" name="parent" not-null="false"/> <bag cascade="none" inverse="true" name="children" order-by="name asc"> <cache usage="read-write"/> <key column="parent_id"/> <one-to-many class="com.tracker.db.entities.Node"/> </bag> </class> </hibernate-mapping>
<ehcache> <!-- Default entity cache --> <defaultCache eternal="false" timeToIdleSeconds="0" timeToLiveSeconds="0" overflowToDisk="false" memoryStoreEvictionPolicy="LFU" /> <cache name="com.tracker.db.entities.Keyword" maxElementsInMemory="1500" /> <cache name="com.tracker.db.entities.Keyword.children" maxElementsInMemory="3000" /> <!-- Default query cache --> <cache name="org.hibernate.cache.StandardQueryCache" maxElementsInMemory="2500" eternal="false" timeToLiveSeconds="900" overflowToDisk="false" /> <!-- Default timestamps cache --> <cache name="org.hibernate.cache.UpdateTimestampsCache" maxElementsInMemory="2500" eternal="true" overflowToDisk="false" /> </ehcache>
Query q = getSession().createQuery("select distinct k from Node n left join fetch n.children left join fetch n.parent"); q.setCacheable(true); Node rootNode = null; for (int i = 0; i < l.size(); i++) { Node n = (Node) l.get(i); //This will make sure that Hibernate caches the children collection Hibernate.initialize(n.getChildren()); //If the current Node has no parent, regard it as the topmost node in the Node hierarchy. if (n.getParent() == null) { rootNode = n; } } return rootNode;