如何使用Hibernate对树进行建模?

2022-09-02 00:55:36

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

我需要能够确定子域和根域。子域本身可以有子域。这可以是相当高的层次。

例:

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

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


答案 1

建模将非常简单:

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

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

查询并不是微不足道的,因为SQL(以及HQL和JPQL)不容易支持树查询。Hibernate可以通过懒惰地加载下一个级别来做到这一点,但是如果你想在一个查询中加载一堆级别,那就是它变得困难的地方。


答案 2

如果要使用Hibernate/JPA惰性初始化(这是正常情况),则不应使用复合模式

与复合模式相关的休眠问题是:在复合中,您有一个复合,它具有对其子组件的引用。但组件只是一个抽象类或接口,因此每个组件都是叶或复合。如果您现在对复合的 cild 集使用延迟初始化,但随后需要如何将混凝土子项转换为叶或复合,则将得到一个“转换异常”,因为休眠对不能转换为“叶”或“复合”的组件使用代理。

复合模式的第二个缺点是,每个类在其整个生存期内都是 Leaf 或复合。如果你的结构永远不会改变,那很好。但是,如果叶必须成为复合节点,因为有人想要添加子节点/叶,它将不起作用。


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

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

推荐