@OneToMany和复合主键?

我正在使用带有注释的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 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]] 

我在我自己的代码中填充了namepos属性,但当然我不知道父ID,因为它尚未保存。 关于如何说服hibernate填写这个的任何想法?

谢谢!

Manning一书Java Persistence with Hibernate在第7.2节中有一个例子,概述了如何做到这一点。 幸运的是,即使您不拥有该书,也可以通过下载此版本的Caveat Emptor示例项目( 此处为直接链接)并检查auction.model包中的CategoryCategorizedItem类来查看源代码示例。 。

我还将总结下面的关键注释。 如果它仍然不合适,请告诉我。

ParentObject:

 @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 attrs; public Long getId () { return id; } public List getAttrs () { return attrs; } } 

ChildObject:

 @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:

(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")就行了。

首先,在ParentObject ,“修复”应设置为"parent"mappedBy属性。 另外(但这可能是一个错字)添加@Id注释:

 @Entity public class ParentObject { @Id @GeneratedValue private String id; @OneToMany(mappedBy="parent", fetch=FetchType.EAGER) @IndexColumn(name = "pos", base=0) private List attrs; // getters/setters } 

然后,在ObjectChild ,将name属性添加到组合键中的objectId

 @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 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) } 

看起来你已经非常接近了,我正在尝试在我当前的系统中做同样的事情。 我从代理键开始,但想要删除它,而不是由父代的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的问题。

保存Parent对象后,必须在Child对象中显式设置parentId才能使Child对象上的插入工作。

花了三天时间,我认为我找到了解决方案,但说实话,我不喜欢它,它肯定可以改进。 但是,它起作用并解决了我们的问题。

这是您的实体构造函数,但您也可以在setter方法中执行此操作。 另外,我使用了Collection对象,但它应该与List相同或类似:

 ... public ParentObject(Collection children) { Collection occ = new ArrayList(); for(ObjectChild obj:children){ obj.setParent(this); occ.add(obj); } this.attrs = occ; } ... 

基本上,正如其他人建议的那样,我们必须首先手动设置所有孩子的父ID,然后再保存父母(以及所有孩子)