@OneToMany and composite primary keys?

点点圈 提交于 2019-11-27 07:06:03
RTBarnard

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<ChildObject> attrs;

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

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.

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<ObjectChild> 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).

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.

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

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.

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.

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<ObjectChild> children) {
    Collection<ObjectChild> occ = new ArrayList<ObjectChild>();
    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)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!