Search code examples
javahibernatejpahibernate-mappinghibernate-annotations

Hibernate Bidirectional one-to-many with composite key


I am trying to create a bidirectional one-to-many association between two entities where the many side has a compound key. And one of the keys of the many side is coming from the one side. Also, I need to have the many side the owner of the association. Below is an example code showing what my code looks like.

Without Jointable

Parent Class which is the one side. I need to have this side owner of the association.

    public class parent{

    @Id
    @Column(name = "NAME")
    private String name;

    @OneToMany(fetch=FetchType.LAZY)
    @JoinColumns({
                  @JoinColumn(name="NAME", nullable = false),
                  @JoinColumn(name="PARENT", nullable = false)})
    private Set<Child> childs;
}

Child Class which is the many side. Its primary key is "name" and "parent". "parent" is coming from the association.

public class child{

    @EmbeddedId
    @AttributeOverrides({
            @AttributeOverride(name="parent", column=@Column(name="PARENT", nullable=false)),
            @AttributeOverride(name="name", column=@Column(name="NAME", nullable=false))})
    private ChildId id;

    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumns({
                   @JoinColumn(name="PARENT", nullable = false, updatable = false, insertable = false),
                   @JoinColumn(name="NAME", nullable = false, updatable = false, insertable = false)})
    private Parent parent;
}

ChildId is the Embedded id.

@Embeddable
public class childId{

    @Column(name = "PARENT")
    private String parent;

    @Column(name = "NAME")
    private String name;
}

With Jointable

Parent Class

    public class parent{

    @Id
    @Column(name = "NAME")
    private String name;

    @OneToMany(fetch = FetchType.LAZY)
    @JoinTable(name="PARENTCHILD",
                joinColumns= {@JoinColumn(name="PNAME", referencedColumnName = "NAME", nullable = false)},
                inverseJoinColumns = {
                        @JoinColumn(name="CNAME", referencedColumnName = "NAME", nullable = false),
                        @JoinColumn(name="CPNAME", referencedColumnName = "PARENT", nullable = false)})
    private Set<Child> childs;
}

Child Class

public class child{

    @EmbeddedId
    @AttributeOverrides({
            @AttributeOverride(name="parent", column=@Column(name="PARENT", nullable=false)),
            @AttributeOverride(name="name", column=@Column(name="NAME", nullable=false))})
    private ChildId id;

    @MapsId("parent")
    @ManyToOne(fetch=FetchType.LAZY)
    @JoinTable(name="PARENTCHILD",
                inverseJoinColumns = {@JoinColumn(name="PNAME", referencedColumnName = "NAME", nullable = false)},
                joinColumns = {
                        @JoinColumn(name="CNAME", referencedColumnName = "NAME", nullable = false),
                        @JoinColumn(name="CPNAME", referencedColumnName = "PARENT", nullable = false)})
    private Parent parent;
}

Question1: This code doesn't work. In case of "without jointable", it gives the below exception.

Caused by: org.hibernate.AnnotationException: A Foreign key refering com.entity.Parent from com.entity.Child has the wrong number of column. should be 1

Question2: And in case of "with jointable", it gives below exception:

SQLCODE=-530, SQLSTATE=-23503, SQLERRMC=PARENTCHILD.FK_PARENTCHILD_CHILD

Solution

  • You don't need to keep Parent name as Id separately in Child , Hibernate will do that for you . I have made a simpler design .And you can control the relation by using mappedBy = childs ,in the @ManyToOne or mappedBy = parent in the @ManyToOne side.

    @Entity
        public class Parent{
    
        @Id
        private String name;
    
        @OneToMany(fetch= FetchType.LAZY)
        private Set<Child> childs;
    
        public Parent(String name) {
            this.name = name;
        }
        public Parent(){}
    }
    
    @Entity
    public class Child{
    
        @Id
        private String name;
    
        @ManyToOne(fetch=FetchType.LAZY)
        private Parent parent;
    }
    

    Three tables will be generated by Hibernate

    Child Table with columns name(Primary Key), parent_name(Foreign Key) Parent Table with one column name(Primary Key) Parent_child table with two columns parent_name and child_name

    EDIT : Solution changed as per amir's needs, just add mappedBy whichever side you need to control the relationship.

    @Entity
    public class Child implements Serializable {
    
        @Id
        private String name;
    
        @Id
        @ManyToOne(fetch=FetchType.LAZY)
        private Parent parent;
    }
    
    @Entity
    public class Parent{
    
        @Id
        private String name;
    
        @OneToMany(fetch= FetchType.LAZY)
        private Set<Child> childs;
    
        public Parent(String name) {
            this.name = name;
        }
        public Parent(){}
    }
    

    EDIT - To name column in child side

    @Id()
    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="xyz")
    private Parent parent;