36

我正在使用带有注释的 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 部分,所以我m 得到一个非唯一错误:

org.hibernate.NonUniqueObjectException:
   a different object with the same identifier value was already associated 
   with the session: [org.kpruden.ObjectChild#null.attr1[0]]

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

谢谢!

4

9 回答 9

20

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

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

父对象:

@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; }
}
于 2010-04-10T00:41:58.800 回答
12

您应该将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")它就可以了。

于 2013-11-15T13:27:00.037 回答
4

首先,在 中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添加一个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

}

AND也添加insertable = false, updatable = false到,@JoinColumn因为我们parentId在此实体的映射中重复列。

通过这些更改,持久化和读取实体对我来说效果很好(用 Derby 测试)。

于 2010-04-10T01:40:36.120 回答
3

After much experimentation and frustration, I eventually determined that I cannot do exactly what I want.

Ultimately, I went ahead and gave the child object its own synthetic key and let Hibernate manage it. It's a not ideal, since the key is almost as big as the rest of the data, but it works.

于 2010-04-15T18:58:41.740 回答
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)
}
于 2015-08-12T23:23:41.767 回答
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的问题。

于 2010-10-01T18:48:01.897 回答
0

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

于 2013-01-31T13:20:33.907 回答
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 回答的内容之上(2010 年 4 月 10 日 1:40 回答)

请参考他的帖子中的示例代码片段,在这个线程的前面。

谢谢,PJR。

于 2020-02-24T22:47:23.413 回答
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,然后再保存父母(连同所有孩子)

于 2017-04-14T14:56:31.967 回答