[英]@OneToMany and composite primary keys?
我正在使用帶有注釋的 Hibernate(在 spring 中),並且我有一個對象,它具有有序的多對一關系,其中一個子對象具有復合主鍵,其中一個組件是返回到父對象的 id。
結構看起來像這樣:
+=============+ +================+
| ParentObj | | ObjectChild |
+-------------+ 1 0..* +----------------+
| id (pk) |-----------------| parentId |
| ... | | name |
+=============+ | pos |
| ... |
+================+
我嘗試了多種注釋組合,但似乎都不起作用。 這是我能想到的最接近的:
@Entity
public class ParentObject {
@Column(nullable=false, updatable=false)
@Id @GeneratedValue(generator="...")
private String id;
@OneToMany(mappedBy="parent", fetch=FetchType.EAGER, cascade={CascadeType.ALL})
@IndexColumn(name = "pos", base=0)
private List<ObjectChild> attrs;
...
}
@Entity
public class ChildObject {
@Embeddable
public static class Pk implements Serializable {
@Column(nullable=false, updatable=false)
private String parentId;
@Column(nullable=false, updatable=false)
private String name;
@Column(nullable=false, updatable=false)
private int pos;
@Override
public String toString() {
return new Formatter().format("%s.%s[%d]", parentId, name, pos).toString();
}
...
}
@EmbeddedId
private Pk pk;
@ManyToOne
@JoinColumn(name="parentId")
private ParentObject parent;
...
}
經過長時間的實驗,我得出了這個結論,其中我的大多數其他嘗試產生了由於各種原因休眠甚至無法加載的實體。
更新:感謝大家的評論; 我取得了一些進展。 我做了一些調整,我認為它更接近(我已經更新了上面的代碼)。 然而,現在問題出在插入上。 父對象似乎保存得很好,但子對象沒有保存,我已經能夠確定的是休眠沒有填寫子對象的(復合)主鍵的 parentId 部分,所以我我得到一個不唯一的錯誤:
org.hibernate.NonUniqueObjectException:
a different object with the same identifier value was already associated
with the session: [org.kpruden.ObjectChild#null.attr1[0]]
我在自己的代碼中填充name
和pos
屬性,但當然我不知道父 ID,因為它還沒有保存。 關於如何說服 hibernate 填寫此內容的任何想法?
謝謝!
Manning 書籍Java Persistence with Hibernate有一個示例,概述了如何在第 7.2 節中執行此操作。 幸運的是,即使您沒有這本書,您也可以通過下載Caveat Emptor示例項目的 JPA 版本(直接鏈接在這里)並檢查auction.model
包中的Category
和CategorizedItem
類來查看源代碼示例.
我還將總結下面的關鍵注釋。 如果仍然不行,請告訴我。
父對象:
@Entity
public class ParentObject {
@Id @GeneratedValue
@Column(name = "parentId", nullable=false, updatable=false)
private Long id;
@OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
@IndexColumn(name = "pos", base=0)
private List<ChildObject> attrs;
public Long getId () { return id; }
public List<ChildObject> getAttrs () { return attrs; }
}
子對象:
@Entity
public class ChildObject {
@Embeddable
public static class Pk implements Serializable {
@Column(name = "parentId", nullable=false, updatable=false)
private Long objectId;
@Column(nullable=false, updatable=false)
private String name;
@Column(nullable=false, updatable=false)
private int pos;
...
}
@EmbeddedId
private Pk id;
@ManyToOne
@JoinColumn(name="parentId", insertable = false, updatable = false)
@org.hibernate.annotations.ForeignKey(name = "FK_CHILD_OBJECT_PARENTID")
private ParentObject parent;
public Pk getId () { return id; }
public ParentObject getParent () { return parent; }
}
您應該將ParentObject
引用合並到ChildObject.Pk
而不是分別映射 parent 和 parentId:
(getter、setter、Hibernate 屬性與問題無關,省略了成員訪問關鍵字)
class ChildObject {
@Embeddable
static class Pk {
@ManyToOne...
@JoinColumn(name="parentId")
ParentObject parent;
@Column...
String name...
...
}
@EmbeddedId
Pk id;
}
在ParentObject
您只需放置@OneToMany(mappedBy="id.parent")
就可以了。
首先,在ParentObject
,“修復” mappedBy
應設置屬性"parent"
。 另外(但這可能是一個錯字)添加一個@Id
注釋:
@Entity
public class ParentObject {
@Id
@GeneratedValue
private String id;
@OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
@IndexColumn(name = "pos", base=0)
private List<ObjectChild> attrs;
// getters/setters
}
然后,在ObjectChild
,為復合鍵中的objectId
添加一個name
屬性:
@Entity
public class ObjectChild {
@Embeddable
public static class Pk implements Serializable {
@Column(name = "parentId", nullable = false, updatable = false)
private String objectId;
@Column(nullable = false, updatable = false)
private String name;
@Column(nullable = false, updatable = false)
private int pos;
}
@EmbeddedId
private Pk pk;
@ManyToOne
@JoinColumn(name = "parentId", insertable = false, updatable = false)
private ParentObject parent;
// getters/setters
}
並且還將insertable = false, updatable = false
@JoinColumn
insertable = false, updatable = false
到@JoinColumn
因為我們在此實體的映射中重復parentId
列。
通過這些更改,持久化和讀取實體對我來說效果很好(用 Derby 測試)。
經過大量的實驗和挫折之后,我最終確定我不能完全按照自己的意願行事。
最終,我繼續為子對象提供了自己的合成密鑰,並讓 Hibernate 管理它。 這並不理想,因為密鑰幾乎與其余數據一樣大,但它有效。
發現這個問題正在尋找它的問題的答案,但它的答案並沒有解決我的問題,因為我正在尋找@OneToMany
,它不太適合我所追求的表結構。 @ElementCollection
適合我的情況。 我相信它的一個問題是它認為整個關系行都是唯一的,而不僅僅是行 ID。
@Entity
public class ParentObject {
@Column(nullable=false, updatable=false)
@Id @GeneratedValue(generator="...")
private String id;
@ElementCollection
@CollectionTable( name = "chidren", joinColumns = @JoinColumn( name = "parent_id" ) )
private List<ObjectChild> attrs;
...
}
@Embeddable
public static class ObjectChild implements Serializable {
@Column(nullable=false, updatable=false)
private String parentId;
@Column(nullable=false, updatable=false)
private String name;
@Column(nullable=false, updatable=false)
private int pos;
@Override
public String toString() {
return new Formatter().format("%s.%s[%d]", parentId, name, pos).toString();
}
... getters and setters REQUIRED (at least they were for me)
}
保存 Parent 對象后,您必須在 Child 對象中顯式設置 parentId,以便 Child 對象上的插入工作。
看起來你已經很接近了,我正在嘗試在我當前的系統中做同樣的事情。 我從代理鍵開始,但想刪除它以支持由父項的 PK 和列表中的索引組成的復合主鍵。
通過使用“外部”生成器,我能夠獲得從主表共享 PK 的一對一關系:
@Entity
@GenericGenerator(
name = "Parent",
strategy = "foreign",
parameters = { @Parameter(name = "property", value = "parent") }
)
public class ChildObject implements Serializable {
@Id
@GeneratedValue(generator = "Parent")
@Column(name = "parent_id")
private int parentId;
@OneToOne(mappedBy = "childObject")
private ParentObject parentObject;
...
}
不知道能不能加上@GenericGenerator 和@GeneratedValue 來解決Hibernate在插入時不分配父級新獲取的PK的問題。
花了三天的時間,我想我找到了一個解決方案,但老實說,我不喜歡它,它肯定可以改進。 但是,它有效並解決了我們的問題。
這是您的實體構造函數,但您也可以在 setter 方法中執行此操作。 另外,我使用了一個 Collection 對象,但它應該與 List 相同或相似:
...
public ParentObject(Collection<ObjectChild> children) {
Collection<ObjectChild> occ = new ArrayList<ObjectChild>();
for(ObjectChild obj:children){
obj.setParent(this);
occ.add(obj);
}
this.attrs = occ;
}
...
基本上,正如其他人所建議的,在保存父級(以及所有子級)之前,我們必須首先手動設置所有子級的父 ID
我一直在尋找答案,但找不到可行的解決方案。 雖然我在父中正確地擁有 OneToMany 並且在子中正確地擁有 ManyToOne,但在父保存期間,未分配子鍵,即父自動生成的值。
在子實體(Java 類)中的 @ManyToOne 映射上方添加注釋 javax.persistence.MapsId 后,我的問題得到解決
@MapsId("java_field_name_of_child's_composite_key_that_needs_the_value_from_parent")
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "PARENT_ID", nullable = false, insertable = false, updatable = false)
private Parent parent;
這是在@Pascal Thivent 回答的基礎上(於 2010 年 4 月 10 日 1:40 回答)
請參閱本主題前面他的帖子中的示例代碼片段。
謝謝,PJR。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.