可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I'm using Hibernate with annotations (in spring), and I have an object which has an ordered, many-to-one relationship which a child object which has a composite primary key, one component of which is a foreign key back to the id of the parent object.
The structure looks something like this:
+=============+ +================+ | ParentObj | | ObjectChild | +-------------+ 1 0..* +----------------+ | id (pk) |-----------------| parentId | | ... | | name | +=============+ | pos | | ... | +================+
I've tried a variety of combinations of annotations, none of which seem to work. This is the closest I've been able to come up with:
@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; ... }
I arrived at this after a long bout of experimentation in which most of my other attempts yielded entities which hibernate couldn't even load for various reasons.
UPDATE: Thanks all for the comments; I have made some progress. I've made a few tweaks and I think it's closer (I've updated the code above). Now, however, the issue is on insert. The parent object seems to save fine, but the child objects are not saving, and what I've been able to determine is that hibernate is not filling out the parentId part of the (composite) primary key of the child objects, so I'm getting a not-unique error:
org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [org.kpruden.ObjectChild#null.attr1[0]]
I'm populating the name
and pos
attributes in my own code, but of course I don't know the parent ID, because it hasn't been saved yet. Any ideas on how to convince hibernate to fill this out?
Thanks!
回答1:
The Manning book Java Persistence with Hibernate has an example outlining how to do this in Section 7.2. Fortunately, even if you don't own the book, you can see a source code example of this by downloading the JPA version of the Caveat Emptor sample project (direct link here) and examining the classes Category
and CategorizedItem
in the auction.model
package.
I'll also summarize the key annotations below. Do let me know if it's still a no-go.
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; } }
回答2:
Firstly, in the ParentObject
, "fix" the mappedBy
attribute that should be set to "parent"
. Also (but this is maybe a typo) add an @Id
annotation:
@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 }
Then, in ObjectChild
, add a name
attribute to the objectId
in the composite key:
@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 also add insertable = false, updatable = false
to the @JoinColumn
because we are repeating the parentId
column in the mapping of this entity.
With these changes, persisting and reading the entities is working fine for me (tested with Derby).
回答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.
回答4:
You should incorporate the ParentObject
reference just into ChildObject.Pk
rather than map parent and parentId separately:
(getters, setters, Hibernate attributes not related to problem and member access keywords omitted)
class ChildObject { @Embeddable static class Pk { @ManyToOne... @JoinColumn(name="parentId") ParentObject parent; @Column... String name... ... } @EmbeddedId Pk id; }
In ParentObject
you then just put @OneToMany(mappedBy="id.parent")
and it works.
回答5:
Found this question searching for the answer to it's problem, but it's answers didn't solve my problem, because I was looking for @OneToMany
which isn't as good of a fit for the table structure I was going after. @ElementCollection
is the right fit in my case. One of the gotchas of it I believe though is that it looks at the entire row of relations as being unique, not just the rows 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) }
回答6:
It seems that you got pretty close, and I am trying to do the same thing in my current system. I started with the surrogate key but would like to remove it in favor of a composite primary key consisting of the parent's PK and the index in the list.
I was able to get a one-to-one relationship that shares the PK from the master table by using a "foreign" generator:
@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; ... }
I wonder if you could add the @GenericGenerator and @GeneratedValue to solve the problem of Hibernate not assigning the parent's newly acquired PK during insertion.
回答7:
After saving the Parent object, you have to explicitly set the parentId in the Child objects for the inserts on the Child objects to work.
回答8:
After spending three days on this, I think I have found a solution, but to be honest, I don't like it and it can definitely be improved. However, it works and solves our problem.
Here is your entity constructor, but you could also do it in the setter method. Also, I used a Collection object but it should be same or similar with List:
... public ParentObject(Collection children) { Collection occ = new ArrayList(); for(ObjectChild obj:children){ obj.setParent(this); occ.add(obj); } this.attrs = occ; } ...
Basically, as someone else suggested, we must first manually set all the children's parent id before saving the parent (along with all children)