开发者

Load parent/child hierarchy with hibernate controlling leafs

开发者 https://www.devze.com 2023-04-02 23:55 出处:网络
I am trying to load from a database a graph of parent/child objects (similar to the DefaultMutableTreeNode object of Java). There is a simple one-to-many association between the 2. The total number of

I am trying to load from a database a graph of parent/child objects (similar to the DefaultMutableTreeNode object of Java). There is a simple one-to-many association between the 2. The total number of levels of the graph is known so i know exactly how many times to invoke the 'getChildren()' method. What i want to do is to NOT call this method for the actual leaf nodes. Usually the graph consists of a few non-leaf nodes and s开发者_运维问答everal hundreds leaf nodes. If i specify lazy=false in the hb mapping, i get hundreds of unnecessary queries from hb for the children of leaf nodes, whereas i know beforehand that they are not needed (since i know the total number of levels on the tree). Unfortunately i cannot use lazy=true and only loop until the parents of the leaf nodes because i am working on a disconnected client/server model and using beanlib to load the whole object graph (that contains several other objects).

So i am trying to find a way to intercept the loading of the 'children' collection and instruct hb to stop when it reaches the leaf nodes. Is there a way to do that? I am looking at 2 solutions: What i have in mind is this: when i call the node.getChildren() method (within a hb session), normally hb will perform a db query to get the children: is there a way to intercept this call and just not make it? I know that there are no children so i just want it to fail fast (in fact i don't want to make it at all).

Thank you Costas


Why don't you just use a boolean leaf property, and make your getChildren method return an empty list if leaf is true?

private boolean leaf;

private List<Node> children;

public List<Node> getChildren() {
    if (leaf) {
        return Collection.<Node>emptyList();
    }
    return children;
}


Unless your database is colocated with the java code issueing these queries, it is probably a performance bottleneck to issue a query per node, even if it just a query per inner node. Since you know the maximum levels of the tree (let's assume 3 for the sake of example), the following ought to fetch the entire tree in a single query:

from Node n1
left join n1.children as n2
left join n2.children as n3
left join n3.children as n4

The disadvantage of that method is that the resultset will repeat the data for each inner node for each of its descendants, i.e. the bandwith taken is multiplied by the number of tree levels. If that is an issue because you have many levels, you could enable batch fetching for that collection, or even do something similar by hand:

List<Node> border = Collections.singletonList(rootNode);
while (!border.isEmpty()) {
    List<Integer> ids = new ArrayList<Integer>();
    for (Node n : border) {
        ids.add(n.getId());
    }

    // initialize the children collection in all nodes in border
    session.createQuery("from Node n left join n.children where n.id in ?").setParameter(0, ids).list();

    List<Node> newBorder = new ArrayList<Node>();
    for (Node n : border) {
        newBorder.addAll(n.getChildren());
    }
    border = newBorder;
}

This will issue as many queries as there are levels in the tree, and transmit the data for each node twice. (Some databases restrict the size of an in-clause. You'd have to batch within the level, then)


You can use AOP around advice around the getChildren call that does something like this (please note this is very rough psuedo-code, you will have to fill in the "blanks"):

childrenResult = node.getChildren()
if (Hibernate.isInitialized(childrenResult)) {
    return node.getChildren()
} else {
  // Do something else here
}

What this will do is when you make a call to getChildren and the collection is not initialized, it can be ingored or not allowed to continue processing. However, if the item is initialized it will allow the calls to continue. One thing to note about Hibernate.isInitialized is that it will return true on ALL objects but lazy-loaded collections that have not been populated yet.

If you are not able to use AOP, you could always do this check on your own call to getChildren in your code.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号