我正在使用带有注释的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;
...
}
经过长时间的一次实验后,我到达了这里,其中我的大多数其他尝试都产生了由于各种原因甚至无法加载的实体。
更新:感谢大家的评论;我取得了一些进展。我做了一些调整,我认为它更接近(我已经更新了上面的代码)。但是,现在问题在于插入。父对象似乎保存得很好,但是子对象没有保存,而我能够确定的是hibernate没有填写子对象的(复合)主键的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填补这一点的任何想法?
谢谢!
答案 0 :(得分:14)
曼宁的书Java Persistence with Hibernate有一个例子,概述了如何在第7.2节中做到这一点。幸运的是,即使您不拥有本书,也可以通过下载Caveat Emptor示例项目的JPA版本(直接链接here)并检查类{{{{{}}来查看源代码示例。 {}}}包中的1}}和Category
。
我还将总结下面的关键注释。如果它仍然不合适,请告诉我。
ParentObject:
CategorizedItem
ChildObject:
auction.model
答案 1 :(得分:4)
您应该将ParentObject
引用合并到ChildObject.Pk
中,而不是分别映射parent和parentId:
(getters,setters,Hibernate属性与问题无关,省略了成员访问关键字)
class ChildObject {
@Embeddable
static class Pk {
@ManyToOne...
@JoinColumn(name="parentId")
ParentObject parent;
@Column...
String name...
...
}
@EmbeddedId
Pk id;
}
在ParentObject
中,您只需添加@OneToMany(mappedBy="id.parent")
即可。
答案 2 :(得分:2)
首先,在ParentObject
中,“修复”应设置为mappedBy
的{{1}}属性。另外(但这可能是一个错字)添加"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
属性添加到复合键中的name
:
objectId
AND 还会将@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
列。
通过这些更改,持久化和读取实体对我来说工作正常(使用Derby测试)。
答案 3 :(得分:2)
经过多次实验和挫折,我最终确定我不能完全按照自己的意愿行事。
最终,我继续为子对象提供了自己的合成密钥,让Hibernate管理它。这是一个不理想的,因为密钥几乎与其他数据一样大,但它可以工作。
答案 4 :(得分:1)
发现这个问题正在寻找问题的答案,但它的答案并没有解决我的问题,因为我正在寻找@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)
}
答案 5 :(得分:0)
看起来你已经非常接近了,我正在尝试在我当前的系统中做同样的事情。我从代理键开始,但想要删除它,而不是使用由父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的问题。
答案 6 :(得分:0)
保存Parent对象后,必须在Child对象中显式设置parentId才能使Child对象上的插入工作。
答案 7 :(得分:0)
花了三天时间,我觉得我找到了一个解决方案,但说实话,我不喜欢它,它肯定可以改进。但是,它起作用并解决了我们的问题。
这是您的实体构造函数,但您也可以在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,然后再保存父母(以及所有孩子)
答案 8 :(得分:0)
我很想寻找答案,但是找不到可行的解决方案。尽管我在父级中正确设置了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(在10年4月10日于1:40回答)所回答的内容之上。
请参考该主题前面的帖子中的示例代码片段。
谢谢, PJR。