如何使用Hibernate建模树?

时间:2011-08-15 12:06:37

标签: java hibernate spring

我有一个名为“Domain”的课程。每个域可以有多个子域(相同类型)。

我需要能够确定子域和根域。子域可以自己拥有子域。这可能会有很多层次。

示例:

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

如何使用Hibernate注释建模这样的Java类?

4 个答案:

答案 0 :(得分:17)

建模非常简单:

@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;
}

请注意,parent是负责数据库条目的属性,即您需要为要存储的关系的子域设置parent

查询不是很简单,因为SQL(以及HQL和JPQL)不容易支持树查询。 Hibernate可以通过懒惰地加载下一级别来实现这一点,但是如果你想在一个查询中加载一堆级别,那就是它变得很难。

答案 1 :(得分:10)

如果你想使用Hibernate / JPA延迟初始化(这是正常情况),那么你应该不使用复合模式

Composite Pattern相关的Hibernate问题是:在Composite中,你有一个Composite,它有一个引用它的子组件。 但Component只是一个抽象类或接口,因此每个Component都是Leaf或Composite。 如果你现在对复合的cild集使用延迟初始化,但是有些人需要将具体的子项转换为Leaf或Composite,你将得到一个Cast Exception,因为hibernate使用代理来处理无法转换为Leaf或复合物。

复合模式的第二个缺点是,每个类在其整个生命周期中都将是Leaf或Composite。 如果您的结构永远不会改变,这很好。但是如果Leaf必须成为Composite,那么它将无法工作,因为有人想添加一个子节点/叶子。


因此,如果你有一些动态结构,我推荐一个类节点,它在父节点和子节点之间具有双向关系。 如果您经常需要导航到代码中的父项或子项,则该关系应该是双向的。 保持这种关系有点棘手,所以我决定发布一些代码。

@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();
    }
}

答案 2 :(得分:2)

我建议您首先获得仅限Java的OO模型,然后担心您使用哪种Hibernate注释。如果您发现需要合法地更改您的Hibernate模型以适应,您可以随时这样做。

最后,这类问题通常是通过Composite pattern的一些变化来解决的(不要过分关注模式事物,只关注它背后的结构和想法。)

使用关系数据库术语(以相当放松的方式):

一个。如果您的域(根域和子域)是 relationship (表中没有重复项且具有可识别主键的n元组集合)

B中。您的域和子域具有相似的结构然后

℃。您可以通过定义“parent”外键将所有这些存储在同一物理表中,以便一个元组的父FK映射到另一个元组的主键。

最重要的是,这种递归关系必须是非循环的。结构上如何解决问题取决于您的问题域(您是否有一个根域,或者您可以拥有多个不相关的根域?)根域可以通过具有NULL父外键或条件来表示其中根域元组的父外键等于其自己的主键。任何一方都有利弊(这是典型的愚蠢火焰战争的主题。)

答案 3 :(得分:1)

根据您需要进行的操作,有几种可能性。

最直接的方法是简单地建立父子一对多关联。根据您的需要,选择合适的类型:单向一对多,单向多对一或双向。

使树的所有节点与根节点具有多对一关系通常很有用。这允许在单个查询中非常容易地加载整个树。