Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to model a tree with Hibernate?

I've got a class called "Domain". Each Domain can have multiple sub-domains (of the same type).

I need to be able to determine sub-domains and root-domains. Sub-domains can have sub-domains themself. This can be quite a few levels deep.

Example:

Rootdomain  
|- Subdomain 1  
|   |- Subdomain 2  
|   |
|   |- Subdomain 3
|
|- Subdomain 4
|   |- Subdomain 5

How do I model such a Java-class with Hibernate annotations?

like image 752
Hedge Avatar asked Aug 15 '11 12:08

Hedge


4 Answers

The modelling would be quite simple:

@Entity
class Domain {
  @ManyToOne //add column definitions as needed
  private Domain parent;      //each Domain with parent==null is a root domain, all others are subdomains

  @OneToMany //add column definitions as needed
  private List<Domain> subdomains;
}

Note that parent is the property responsible for the database entry, i.e. you need to set parent for a subdomain for the relation to be stored.

What is not quite trivial is the queries, since SQL (and thus HQL and JPQL) doesn't easily support tree queries. Hibernate can do this by lazily loading the next level but if you want to load a bunch of levels in one query, that's where it becomes hard.

like image 105
Thomas Avatar answered Nov 19 '22 20:11

Thomas


If you want to use Hibernate/JPA lazy initialization (that is the normal case) then you should not use the composite pattern.

The Hibernate related problem with the Composite Pattern is: in a Composite you have a Composite that has a reference its child Components. But the Component is only a abstract class or Interface, so every Component is a Leaf or a Composite. If you now use lazy initialisation for the cild set of Composite, but then some how need to cast a concrete child to Leaf or Composite, you will get an Cast Exception because hibernate uses a proxy for the component that can not be casted to Leaf or Composite.

The second drawback of the composite pattern is, that every class will be a Leaf or a Composite for its complete lifetime. That is fine if your structure never changes. But it will not work if a Leaf must become to an Composite because someone wants to add an sub-node/leaf.


So if you have some dynamic structure I recommend one class Node that have a bidirectional relatinship between parent and child nodes. The relationship should be bidirectional if you often need to navigate to parents or childs in your code. Maintaining that relationship is a bit tricky, so I decided to post some more code.

@Entity
public class Domain {

    @Id
    private long id;

     /** The parent domain, can be null if this is the root domain. */
    @ManyToOne
    private Domain parent;

    /**
     * The children domain of this domain.
     * 
     * This is the inverse side of the parent relation.
     * 
     * <strong>It is the children responsibility to manage there parents children set!</strong>
     */
    @NotNull
    @OneToMany(mappedBy = "parent")
    private Set<Domain> children = new HashSet<Domain>();
    /**
     * Do not use this Constructor!
     * Used only by Hibernate.
     */
    Domain() {
    }

    /**
     * Instantiates a new domain.
     * The domain will be of the same state like the parent domain.
     *
     * @param parent the parent domain
     * @see Domain#createRoot()
     */
    public Domain(final Domain parent) {
        if(parent==null) throw new IllegalArgumentException("parent required");

        this.parent = parent;
        registerInParentsChilds();
    }

    /** Register this domain in the child list of its parent. */
    private void registerInParentsChilds() {
        this.parent.children.add(this);
    }

    /**
     * Return the <strong>unmodifiable</strong> children of this domain.
     * 
     * @return the child nodes.
     */
    public Set<Domain> getChildren() {
        return Collections.unmodifiableSet(this.children);
    }        

    /**
     * Move this domain to an new parent domain.
     *
     * @param newParent the new parent
     */
    public void move(final Domain newParent)  {
        Check.notNullArgument(newParent, "newParent");

        if (!isProperMoveTarget(newParent) /* detect circles... */ ) { 
            throw new IllegalArgumentException("move", "not a proper new parent", this);
        }

        this.parent.children.remove(this);
        this.parent = newParent;
        registerInParentsChilds();
    }

    /**
     * Creates the root.
     *
     * @param bid the bid
     * @return the domain
     */
    public static Domain createRoot() {
        return new Domain();
    }
}
like image 41
Ralph Avatar answered Nov 19 '22 19:11

Ralph


I'd suggest you first get the Java-only OO model right and then worry about which Hibernate annotations you use. If you find you need to legitimately alter your model for Hibernate to fit in, you can always do so.

In the end, this type of problem is typically solved with some variation of the Composite pattern (don't focus too much on the pattern thing, just on the structure and idea behind it.)

Using relational database lingo (in a rather relaxed manner):

A. If your domains (root domains and sub-domains) are relations (collections of n-tuples in a table with no duplicates and with a discernible primary key) and

B. your domains and sub-domains have similar structure, then

C. You might be able to store all of them in the same physical table by defining a "parent" foreign key such that the parent FK of one tuple maps to the primary key of another one.

Most importantly, this recursive relationship must be acyclic. How you go about it structurally is up to your problem domain (do you have one root domain, or you can have multiple, unrelated root domains?) A root domain can be denotated by having either a NULL parent foreign key, or with a condition where a root domain tuple's parent foreign key equals its own primary key. Either one has pros and cons (which are the subject of typically stupid flame wars.)

like image 4
luis.espinal Avatar answered Nov 19 '22 20:11

luis.espinal


There are several possibilities, depending on which kind of operations you need to make.

The most straightforward is to simply have a parent-child one-to-many association. Depending on what you need to do, choose the appropriate kind: unidirectional one-to-many, unidirectional many-to-one, or bidirectional.

It's often useful to have all the nodes of a tree have a many-to-one relationship with the root node. This allows loading a whole tree very easily, in a single query.

like image 2
JB Nizet Avatar answered Nov 19 '22 20:11

JB Nizet