Le principe de mon arbre est le suivant :

  • 1 arbre (Tree) a un nom et a une référence vers le noeud (Node) 'root'
  • 1 noeud (Node) a un parent, des enfants et contient une référence vers une valeur (NodeValue)
  • la valeur (NodeValue)

Tree :

package com.blog.tree.model;

@Entity
@Table(name = "TREE")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.STRING)
public abstract class Tree<T extends NodeValue & Serializable> implements Serializable {
        private static final long serialVersionUID = -934945451933213736L;
        private Long id;
        private Node<T> root;
        private String name;

        /**
         * 
         */
        public Tree() {
                super();
        }

        @Id
        public Long getId() {
                return this.id;
        }

        public void setId(Long id) {
                this.id = id;
        }

        @OneToOne(cascade = CascadeType.ALL, targetEntity = Node.class)
        public <H extends Node<T>> H getRoot() {
                return (H) this.root;
        }

        public <H extends Node<T>> void setRoot(H root) {
                this.root = root;
        }

        public String getName() {
                return this.name;
        }

        public void setName(String name) {
                this.name = name;
        }
}

Node :

package com.blog.tree.model;

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.STRING)
public abstract class Node<T extends NodeValue & Serializable> implements Serializable {
        private static final long serialVersionUID = -706154548349031973L;
        private Long id;
        private T nodeValue;
        private List<? extends Node<T>> children;
        private Node<T> root;
        private Node<T> parent;
        @SuppressWarnings("unused")
        private int childPosition;

        /**
         * 
         */
        public Node() {
                super();

        }

        @Id
        public Long getId() {
                return this.id;
        }

        public void setId(Long id) {
                this.id = id;
        }

        @OneToMany(cascade = CascadeType.ALL, targetEntity = Node.class)
        @JoinColumn(name = "parentId", nullable = true)
        @OrderBy(value = "childPosition")
        public List<? extends Node<T>> getChildren() {
                return this.children;
        }

        public void setChildren(List<? extends Node<T>> children) {
                this.children = children;
        }

        @OneToOne(cascade = CascadeType.ALL, targetEntity = NodeValue.class)
        public T getNodeValue() {
                return this.nodeValue;
        }

        public void setNodeValue(T nodeValue) {
                this.nodeValue = nodeValue;
        }

        @OneToOne(targetEntity = Node.class)
        @JoinColumn(name = "rootId", nullable = false)
        public <H extends Node<T>> H getRoot() {
                return (H) this.root;
        }

        public <H extends Node<T>> void setRoot(H root) {
                this.root = root;
        }

        public int getChildPosition() {
                if (isRoot()) {
                        return -1;
                }
                List<? extends Node<T>> list = getParent().getChildren();
                return list.indexOf(this);
        }

        public void setChildPosition(int childPosition) {
                this.childPosition = childPosition;
        }

        @ManyToOne(targetEntity = Node.class)
        @JoinColumn(name = "parentId", nullable = true)
        public <H extends Node<T>> H getParent() {
                return (H) this.parent;
        }

        public <H extends Node<T>> void setParent(H parent) {
                this.parent = parent;
        }

        @Transient
        public boolean isRoot() {
                return (parent == null);
        }

        @Transient()
        public boolean isLeaf() {
                return (getChildren() == null || getChildren().size() == 0);
        }
}

NodeValue : sera la sous classe des valeurs des noeuds.

package com.blog.tree.model;

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class NodeValue implements Serializable {
        private static final long serialVersionUID = 153631252194745635L;
        private Long id;

        /**
         * 
         */
        public NodeValue() {
                super();
        }

        @Id
        public Long getId() {
                return this.id;
        }

        public void setId(Long id) {
                this.id = id;
        }

        @Override
        public int hashCode() {
                return new HashCodeBuilder().append(this.getId()).toHashCode();
        }

        @Override
        public boolean equals(Object obj) {
                if (this == obj) {
                        return true;
                }
                if (!(obj instanceof NodeValue)) {
                        return false;
                }
                NodeValue cast = (NodeValue) obj;
                return new EqualsBuilder().append(this.getId(), cast.getId()).isEquals();
        }
}