2011-08-15 47 views
13

我有一個名爲「域」的類。每個域可以有多個子域(相同類型)。如何用Hibernate建模樹?

我需要能夠確定子域和根域。子域可以有自己的子域。這可能是相當深的幾個級別。

例子:

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

如何模擬這樣一個Java類與Hibernate的註解?

回答

14

建模將是相當簡單:

@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可以通過延遲加載下一個級別來實現這一點,但是如果你想在一個查詢中加載一堆級別的話,那就很難了。

+0

不錯,簡短和重點。 –

+0

我不會推薦使用列表。休眠不能同時獲取多個包(列表)。如果您嘗試在同一個查詢中加載3層樹,則需要使用Set。 – Dherik

+1

@Dherik你是對的。儘管使用列表應該可以使用索引。 – Thomas

1

有幾種可能性,這取決於您需要進行哪種操作。

最簡單的就是簡單地有一個親子一對多的關聯。根據您需要執行的操作,選擇適當的類型:單向一對多,單向多對一或雙向。

使樹的所有節點與根節點具有多對一關係通常很有用。這允許在單個查詢中非常容易地加載整個樹。

2

我建議你先得到只有Java的OO模型,然後擔心你使用的Hibernate註釋。如果你發現你需要合理地改變你的模型以適應Hibernate,你總是可以這樣做。

最後,這種類型的問題與Composite pattern的一些變化通常是解決(不要過於注重圖案的東西,只是在結構上和它的設計思想。)

使用關係型數據庫行話(在一個相當輕鬆的方式):

答:如果您的域(根域和子域)是關係(n元組的集合在一個表中,沒有重複,並用明顯的主鍵)

B.您的域名,具有類似的結構子域,然後

下您可能能夠通過定義「父」外鍵,以便對所有這些存儲在同一物理表一個元組的父FK映射到另一個元組的主鍵。

最重要的是,這種遞歸關係必須是非循環的。你如何從結構上去解決問題域(你有一個根域,還是可以有多個不相關的根域?)可以通過具有NULL父外鍵或根域元組的父外鍵等於其自己的主鍵的條件來指定根域。每個人都有優點和缺點(這是典型的愚蠢火焰戰爭的主題)。

+1

我非常不同意複合模式的用法(請參閱我的答案以獲取更多詳細信息)。但是如果你用「一些變化」來解決這個問題,那麼你應該在一些細節上解釋這種變化。 – Ralph

+0

我所說的變化是讓所有葉子和複合材料具有相同的具體類型(詳見托馬斯的答案)。當然,對於「複合模式」意味着什麼的嚴格(因此天真)的解釋會導致許多問題(獨立於圖片中的hibernate)。現實情況是,所有「猶太教」,模式的書本實現也會遇到問題。名稱「複合」不應該引出嚴格不同的葉和複合類型的願景,而不是*可以由其他可能類型等同的東西*組成的東西*。 –

9

如果您想使用Hibernate/JPA惰性初始化(即正常情況),那麼您應該使用不使用複合模式

Composite Pattern Hibernate相關的問題是:在一個複合中,你有一個複合引用其子組件。 但是組件只是一個抽象類或接口,所以每個組件都是一個Leaf或一個Composite。 如果您現在對cild的組合使用lazy初始化,但是接下來需要將具體的子組件轉換爲Leaf或Composite,您將獲得Cast異常,因爲hibernate使用代理來處理無法轉換爲Leaf的組件或複合。

組合模式的第二個缺點是,每個類在其整個生命週期中都是Leaf或Composite。 這很好,如果你的結構永不改變。但是如果一個Leaf必須變成一個組合,因爲有人想要添加一個子節點/葉子,它將不起作用。


所以,如果你有一些動態結構,我推薦一個父節點和子節點之間具有雙向關係的類節點。 如果您經常需要導航到代碼中的父母或孩子,則該關係應該是雙向的。 保持這種關係有點棘手,所以我決定發佈一些更多的代碼。

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

當我刪除一個節點時,是否也會刪除所有的子節點? 'inverse'屬性和'cascade'可以一次保存樹嗎? –

+0

@Aaron Digulla:我的回答目前不包括這個主題。所以目前它不會刪除孩子,如果你刪除它的parant。但是你應該可以通過級聯刪除來實現這一點。我不相信你需要'inverse'屬性來刪除。 – Ralph

+0

我看到至少2個版本,但我想知道哪個更好: 您也可以添加'@JoinColumn註解(name =「parent_id」)',但正如我上面提到的,我不知道這些好處是什麼。 – Zarathustra