有一個實體類「A」。 A類可能有同一類型「A」的孩子。如果它是一個孩子,「A」應該是它的父母。JPA:如何具有相同實體類型的一對多關係
這可能嗎?如果是這樣,我應該如何映射實體類中的關係? [「A」有一個id列。]
有一個實體類「A」。 A類可能有同一類型「A」的孩子。如果它是一個孩子,「A」應該是它的父母。JPA:如何具有相同實體類型的一對多關係
這可能嗎?如果是這樣,我應該如何映射實體類中的關係? [「A」有一個id列。]
是的,這是可能的。這是標準雙向關係的特例。這很特殊,因爲關係的每一端的實體是相同的。一般情況詳見JPA 2.0 spec的第2.10.2節。
這是一個很好的例子。首先,實體類A
:
@Entity
public class A implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
@ManyToOne
private A parent;
@OneToMany(mappedBy="parent")
private Collection<A> children;
// Getters, Setters, serialVersionUID, etc...
}
這裏有一個粗略的main()
方法仍然存在三個這樣的實體:
public static void main(String[] args) {
EntityManager em = ... // from EntityManagerFactory, injection, etc.
em.getTransaction().begin();
A parent = new A();
A son = new A();
A daughter = new A();
son.setParent(parent);
daughter.setParent(parent);
parent.setChildren(Arrays.asList(son, daughter));
em.persist(parent);
em.persist(son);
em.persist(daughter);
em.getTransaction().commit();
}
在這種情況下,事務提交前所有三個實體實例都必須堅持。如果我無法堅持父子關係圖中的一個實體,則會在commit()
上拋出異常。在Eclipselink上,這是詳細說明不一致的RollbackException
。
此行爲可通過A
的@OneToMany
和@ManyToOne
註釋上的cascade
屬性進行配置。例如,如果我在這兩個註釋上設置了cascade=CascadeType.ALL
,我可以安全地堅持其中一個實體並忽略其他實體。說我在我的交易中堅持parent
。 JPA實施遍歷parent
的children
屬性,因爲它標記爲CascadeType.ALL
。 JPA實現在那裏找到son
和daughter
。然後,我代表我的兩個孩子堅持,儘管我沒有明確要求。
還有一點需要注意。更新雙向關係的雙方始終是程序員的責任。換句話說,無論何時向某位父母添加一個孩子,我都必須相應地更新孩子的父母屬性。只更新雙向關係的一側是JPA下的錯誤。始終更新關係的雙方。這明確地寫在JPA 2.0規範的第42頁:
注意,它是承擔責任,維護運行時的一致性應用的關係,例如,用於確保了「一」和「多「當應用程序在運行時更新關係時,雙向關係的邊相互一致。
對我來說訣竅是使用多對多的關係。假設你的實體A是一個可以進行細分的部門。然後(跳過不相關的細節):(基於關係模型)
@Entity
@Table(name = "DIVISION")
@EntityListeners({ HierarchyListener.class })
public class Division implements IHierarchyElement {
private Long id;
@Id
@Column(name = "DIV_ID")
public Long getId() {
return id;
}
...
private Division parent;
private List<Division> subDivisions = new ArrayList<Division>();
...
@ManyToOne
@JoinColumn(name = "DIV_PARENT_ID")
public Division getParent() {
return parent;
}
@ManyToMany
@JoinTable(name = "DIVISION", joinColumns = { @JoinColumn(name = "DIV_PARENT_ID") }, inverseJoinColumns = { @JoinColumn(name = "DIV_ID") })
public List<Division> getSubDivisions() {
return subDivisions;
}
...
}
因爲我有大約層次結構和JPA一些廣泛的業務邏輯是非常弱的,以支持它,我已經被引入接口IHierarchyElement
和實體監聽HierarchyListener
:
public interface IHierarchyElement {
public String getNodeId();
public IHierarchyElement getParent();
public Short getLevel();
public void setLevel(Short level);
public IHierarchyElement getTop();
public void setTop(IHierarchyElement top);
public String getTreePath();
public void setTreePath(String theTreePath);
}
public class HierarchyListener {
@PrePersist
@PreUpdate
public void setHierarchyAttributes(IHierarchyElement entity) {
final IHierarchyElement parent = entity.getParent();
// set level
if (parent == null) {
entity.setLevel((short) 0);
} else {
if (parent.getLevel() == null) {
throw new PersistenceException("Parent entity must have level defined");
}
if (parent.getLevel() == Short.MAX_VALUE) {
throw new PersistenceException("Maximum number of hierarchy levels reached - please restrict use of parent/level relationship for "
+ entity.getClass());
}
entity.setLevel(Short.valueOf((short) (parent.getLevel().intValue() + 1)));
}
// set top
if (parent == null) {
entity.setTop(entity);
} else {
if (parent.getTop() == null) {
throw new PersistenceException("Parent entity must have top defined");
}
entity.setTop(parent.getTop());
}
// set tree path
try {
if (parent != null) {
String parentTreePath = StringUtils.isNotBlank(parent.getTreePath()) ? parent.getTreePath() : "";
entity.setTreePath(parentTreePath + parent.getNodeId() + ".");
} else {
entity.setTreePath(null);
}
} catch (UnsupportedOperationException uoe) {
LOGGER.warn(uoe);
}
}
}
爲什麼不使用簡單的@OneToMany(mappedBy =「DIV_PARENT_ID」)而不是@ManyToMany(...)和自引用屬性?重新調整表格和列名稱會違反DRY。也許這是有原因的,但我沒有看到它。另外,EntityListener示例是整潔但不可移植的,假設'Top'是一種關係。 JPA 2.0規範的第93頁,實體監聽器和回調方法:「通常,可移植應用程序的生命週期方法不應該調用EntityManager或Query操作,訪問其他實體實例或修改關係。對?讓我知道我是否離開。 – 2010-08-03 06:05:31
我的解決方案是3歲使用JPA 1.0。我從生產代碼中改變了它。我確信我可以列出一些列名,但這不是重點。你的答案完全簡單,不知道爲什麼當時我使用了多對多的方法 - 但它確實有效,我相信更復雜的解決方案是有原因的。儘管如此,我現在不得不重新訪問。 – topchef 2010-08-03 06:37:49
是的,top是一個自我引用,因此是一種關係。嚴格地說,我不修改它 - 只是初始化。而且,它是單向的,所以沒有其他方式的依賴關係,它不會引用除自我之外的其他實體。根據你的報價規範有「一般」,這意味着它不是嚴格的定義。如果有的話,我相信在這種情況下可移植性風險非常低。 – topchef 2010-08-03 06:44:53
非常感謝您的詳細解釋!這個例子是關鍵,並在第一次運行。 – sanjayav 2010-08-03 06:11:00
@sunnyj很高興幫助。祝你的項目好運。 – 2010-08-03 06:22:29
在創建具有子類別的類別實體之前,它遇到了此問題。這很有幫助! – 2010-08-03 06:33:28