我有一個名爲「域」的類。每個域可以有多個子域(相同類型)。如何用Hibernate建模樹?
我需要能夠確定子域和根域。子域可以有自己的子域。這可能是相當深的幾個級別。
例子:
Rootdomain
|- Subdomain 1
| |- Subdomain 2
| |
| |- Subdomain 3
|
|- Subdomain 4
| |- Subdomain 5
如何模擬這樣一個Java類與Hibernate的註解?
我有一個名爲「域」的類。每個域可以有多個子域(相同類型)。如何用Hibernate建模樹?
我需要能夠確定子域和根域。子域可以有自己的子域。這可能是相當深的幾個級別。
例子:
Rootdomain
|- Subdomain 1
| |- Subdomain 2
| |
| |- Subdomain 3
|
|- Subdomain 4
| |- Subdomain 5
如何模擬這樣一個Java類與Hibernate的註解?
建模將是相當簡單:
@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可以通過延遲加載下一個級別來實現這一點,但是如果你想在一個查詢中加載一堆級別的話,那就很難了。
有幾種可能性,這取決於您需要進行哪種操作。
最簡單的就是簡單地有一個親子一對多的關聯。根據您需要執行的操作,選擇適當的類型:單向一對多,單向多對一或雙向。
使樹的所有節點與根節點具有多對一關係通常很有用。這允許在單個查詢中非常容易地加載整個樹。
我建議你先得到只有Java的OO模型,然後擔心你使用的Hibernate註釋。如果你發現你需要合理地改變你的模型以適應Hibernate,你總是可以這樣做。
最後,這種類型的問題與Composite pattern的一些變化通常是解決(不要過於注重圖案的東西,只是在結構上和它的設計思想。)
使用關係型數據庫行話(在一個相當輕鬆的方式):
答:如果您的域(根域和子域)是關係(n元組的集合在一個表中,沒有重複,並用明顯的主鍵)和
B.您的域名,具有類似的結構子域,然後
下您可能能夠通過定義「父」外鍵,以便對所有這些存儲在同一物理表一個元組的父FK映射到另一個元組的主鍵。
最重要的是,這種遞歸關係必須是非循環的。你如何從結構上去解決問題域(你有一個根域,還是可以有多個不相關的根域?)可以通過具有NULL父外鍵或根域元組的父外鍵等於其自己的主鍵的條件來指定根域。每個人都有優點和缺點(這是典型的愚蠢火焰戰爭的主題)。
我非常不同意複合模式的用法(請參閱我的答案以獲取更多詳細信息)。但是如果你用「一些變化」來解決這個問題,那麼你應該在一些細節上解釋這種變化。 – Ralph
我所說的變化是讓所有葉子和複合材料具有相同的具體類型(詳見托馬斯的答案)。當然,對於「複合模式」意味着什麼的嚴格(因此天真)的解釋會導致許多問題(獨立於圖片中的hibernate)。現實情況是,所有「猶太教」,模式的書本實現也會遇到問題。名稱「複合」不應該引出嚴格不同的葉和複合類型的願景,而不是*可以由其他可能類型等同的東西*組成的東西*。 –
如果您想使用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();
}
}
當我刪除一個節點時,是否也會刪除所有的子節點? 'inverse'屬性和'cascade'可以一次保存樹嗎? –
@Aaron Digulla:我的回答目前不包括這個主題。所以目前它不會刪除孩子,如果你刪除它的parant。但是你應該可以通過級聯刪除來實現這一點。我不相信你需要'inverse'屬性來刪除。 – Ralph
我看到至少2個版本,但我想知道哪個更好: 您也可以添加'@JoinColumn註解(name =「parent_id」)',但正如我上面提到的,我不知道這些好處是什麼。 – Zarathustra
不錯,簡短和重點。 –
我不會推薦使用列表。休眠不能同時獲取多個包(列表)。如果您嘗試在同一個查詢中加載3層樹,則需要使用Set。 – Dherik
@Dherik你是對的。儘管使用列表應該可以使用索引。 – Thomas