Hibernate Annotations for a One-To-Many Mapping Featuring a Many Side, Composite, Primary Key having a Composite, Foreign, Sub-Key

Sure, that’s a honking title, but it is what’s needed to describe a Hibernate mapping that recently required some head scratching on my part to figure out. I’ve been off of Hibernate (as in off of the wagon) for a while now as I’ve been busy writing stored procedures. Now that I’ve fallen back on the wagon, and am working on a Spring/Hibernate project, I’ve started to recall what a joy/pain Hibernate can be.

This project has a legacy database so I had to map the database objects as is. The relationship is between a parent and child table (or master/detail set of tables), which I’m going to refer to as Parent and Child, respectively:

Parent and Child Tables

As the title of this post indicates Parent has a composite primary key. The interesting, and tricky to handle in Hibernate item is the primary key of the child table. This is also a composite key, and a sub-key of the Child primary key is a foreign key to Parent. The DDL for this relationship gives an exact picture of the relationship:

create table parent (
  id      integer,
  version integer,
  constraint parent_pk primary key (id, version)
);

create table child (
  id      integer,
  parent_id integer,
  parent_version integer,
  constraint child_pk
    primary key (id, parent_id, parent_version),
  constraint child_fk
    foreign key (parent_id, parent_version)
    references parent (id, version)
);

Two fields from Child (parent_id and parent_version ) point back to the owning Parent (id, version). There are a couple of aspects that make this harder to model from the Hibernate perspective. All of the keys involved are composite, so you have to use embedded objects to represent each key.

Secondly, there is no separation between the link to the parent and the existence of a child instance. Hibernate is most easy to deal with when these are two different items. That is why you see recommendations 1) against unidirectional OneToMany relationships with a foreign key on the owned entity, and 2) and that you use a join table in these cases. The join table lets you separate the existence of a child (a record in the child table) from the existence of the relationship to parent (a record in the join table). In this case, since the foreign key (the link) is part of the primary key (existence), these two concept are not separated and we need to just deal with it.

Against the above recommendation, one has to go with a OneToMany with no join table. I didn’t invent how to model this, the canonical posting for relationships with composite keys are here and here. However, those posting don’t deal with the combined primary and foreign key and when both keys are composite so there is some extra detail here. I’ve packaged the example up as an Eclipse project and the complete source code is available here.

Because the keys are composite, the Java object model has embedded objects that represent the primary key values, as shown in following class diagram:

Parent and Child Class Diagram

The main domain objects are named Parent and Child, which would also hold all of the additional attributes in a real application (I’m showing only key values here for clarity). The primary key objects are ParentPrimaryKey and ChildPrimaryKey. An important point to note here is the representation of the foreign key in ChildPrimaryKey. ChildPrimaryKey holds a reference to the owning Parent as an instance variable (named parentForeignKey).

Moving on to the annotations and the coding tasks that need to be applied to each of classes, we start with Parent:

package com.beavercreekconsulting.example;

import java.util.List;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity
@Table(name = "parent")
public class Parent {

  @Id
  ParentPrimaryKey primaryKey = new ParentPrimaryKey();

  @OneToMany(mappedBy = "primaryKey.parentForeignKey",
    fetch=FetchType.EAGER)
  private List children;

  public ParentPrimaryKey getPrimaryKey() {
    return primaryKey;
  }

  public void setPrimaryKey(ParentPrimaryKey primaryKey) {
    this.primaryKey = primaryKey;
  }

  public List getChildren() {
    return children;
  }

  public void setChildren(List children) {
    this.children = children;
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result
      + ((children == null) ? 0 : children.hashCode());
    result = prime * result
      + ((primaryKey == null) ? 0 : primaryKey.hashCode());
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null) return false;
    if (getClass() != obj.getClass()) return false;
    final Parent other = (Parent) obj;
    if (children == null) {
        if (other.children != null) return false;
    } else if (!children.equals(other.children)) return false;
    if (primaryKey == null) {
      if (other.primaryKey != null) return false;
    } else if (!primaryKey.equals(other.primaryKey)) return false;
    return true;
  }
}

The Entity and Table annotations are straightforward JPA annotations. The Id annotation on the primaryKey instance variable indicates that an embedded object will be used for the primary key.

The @OneToMany annotation with the mappedBy element indicates a bidirectional relationship with the Many side as the owner.

Annotation of the ParentPrimaryKey is straightforward:

package com.beavercreekconsulting.example;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Embeddable;

@Embeddable
public class ParentPrimaryKey implements Serializable {

  private static final long serialVersionUID = -2912693560354598053L;

  @Column(name = "id")
  Integer id;

  @Column(name = "version")
  Integer version;

  public Integer getId() {
    return id;
  }

  public void setId(Integer id) {
    this.id = id;
  }

  public Integer getVersion() {
    return version;
  }

  public void setVersion(Integer version) {
    this.version = version;
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((id == null) ? 0 : id.hashCode());
    result = prime * result + ((version == null) ? 0 : version.hashCode());
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null) return false;
    if (getClass() != obj.getClass()) return false;
    final ParentPrimaryKey other = (ParentPrimaryKey) obj;
    if (id == null) {
      if (other.id != null) return false;
    } else if (!id.equals(other.id)) return false;
    if (version == null) {
      if (other.version != null) return false;
    } else if (!version.equals(other.version)) return false;
    return true;
  }
}

The embeddable class needs to implement Serializable, so I added Eclipse generated serialVersionUID, hashCode() and equals().

The ChildPrimaryKey class is pretty similar to ParentPrimaryKey:

package com.beavercreekconsulting.example;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.ManyToOne;

@Embeddable
public class ChildPrimaryKey implements Serializable {

  private static final long serialVersionUID = -452758257162645576L;

  @Column(name="id")
  Integer id;

  @ManyToOne
  Parent parentForeignKey;

  public Integer getId() {
    return id;
  }

  public void setId(Integer id) {
    this.id = id;
  }

  public Parent getParentForeignKey() {
    return parentForeignKey;
  }

  public void setParentForeignKey(Parent parentForeignKey) {
    this.parentForeignKey = parentForeignKey;
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((id == null) ? 0 : id.hashCode());
    result = prime * result
      + ((parentForeignKey == null) ? 0 : parentForeignKey.hashCode());
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null) return false;
    if (getClass() != obj.getClass()) return false;
    final ChildPrimaryKey other = (ChildPrimaryKey) obj;
    if (id == null) {
      if (other.id != null)
        return false;
    } else if (!id.equals(other.id))
      return false;
    if (parentForeignKey == null) {
      if (other.parentForeignKey != null)
        return false;
    } else if (!parentForeignKey.equals(other.parentForeignKey))
      return false;
    return true;
  }
}

The main annotations are @Column on the id field and @OneToMany on the parentForeignKey field. This class also needs to implement java.io.Serializable, so I added serialVersionUID, hashCode() and equals(). Eclipse pointed out that the hashCode() method might not work correctly due to it’s dependence on parentForeignKey unless Parent also implements hashCode(), so I added that to Parent.

The final class, Child has the most complicated annotations. The goal of the annotations is to point the persistence layer to the embedded primary key object for the purposes of object identification. Here is a quick check list of the annotation that need to be applied:

  1. The instance variable that points to the primary key object should be annotated with @EmbeddedId.
  2. Supply getters and setters for for primary key fields that are annotated with @Transient and delegate the setting and getting to the embedded primary key class.
  3. Use @AssociationOverrides to point the values of the embedded class. The name of the association to override has the form of <instance-var-name>.<embedded-class-property>.
  4. Use an @AssociationOverride for each instance variable involved. You are noting the fields of this class and how they will be mapped into the parent.
  5. For the composite foreign key, use @JoinColumn to point from the database field back to the mapped attribute in the parent object.

I do hope the above points help you to map your object in the unfortunate event that you have to deal with a similar situation. If nothing else, just copy-n-paste-n-edit from the following code:

package com.beavercreekconsulting.example;

import javax.persistence.AssociationOverride;
import javax.persistence.AssociationOverrides;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.Table;
import javax.persistence.Transient;

@Entity
@Table(name = "child")
@AssociationOverrides( {
  @AssociationOverride(name = "primaryKey.id",
    joinColumns = @JoinColumn(name = "id")),
  @AssociationOverride(name = "primaryKey.parentForeignKey",
    joinColumns = {
      @JoinColumn(name = "parent_id",
        referencedColumnName = "id"),
      @JoinColumn(name = "parent_version",
        referencedColumnName = "version") }) })
public class Child {

  @EmbeddedId
  ChildPrimaryKey primaryKey = new ChildPrimaryKey();

  public ChildPrimaryKey getPrimaryKey() {
    return primaryKey;
  }

  public void setPrimaryKey(ChildPrimaryKey primaryKey) {
    this.primaryKey = primaryKey;
  }

  @Transient
  public Integer getId() {
    return getPrimaryKey().getId();
  }

  public void setId(Integer id) {
    getPrimaryKey().setId(id);
  }

  @Transient
  public Parent getParentForeignKey() {
    return getPrimaryKey().getParentForeignKey();
  }

  public void setParentForeignKey(Parent foreignKey) {
    getPrimaryKey().setParentForeignKey(foreignKey);
  }
}

The final section of code I have is an Example class which does a set of CRUD operations with this mapping. I’ve used Spring in the example since I’m starting to believe this is Hibernate’s natural environment:

package com.beavercreekconsulting.example;

import java.util.Arrays;
import java.util.List;
import java.util.Random;

import org.springframework.beans.factory.BeanFactory;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
* Main entry point for example
*/
public class Example {

  HibernateTemplate hibernateTemplate;

  public static void main(String[] args) {
    ClassPathResource resource = new ClassPathResource("beans.xml");
    BeanFactory bf = new XmlBeanFactory(resource);
    Example example = (Example) bf.getBean("example");
    example.go();
  }

  @Transactional(readOnly = false, propagation = Propagation.REQUIRED)
  private void go() {
    create();
    read();
    update();
    delete();
    hibernateTemplate.flush();
  }

  private void update() {
    List parents = hibernateTemplate.find("from Parent");
    for (Parent p : parents) {
        Child c = new Child();
        c.setParentForeignKey(p);
        c.setId(new Random().nextInt());
        p.getChildren().add(c);
        hibernateTemplate.save(c);
    }
  }

  private void read() {
    List parents = hibernateTemplate.find("from Parent");
  }

  private void delete() {
  List parents = hibernateTemplate.find("from Parent");
  for (Parent p : parents) {
    for (Child c : p.getChildren()) {
      hibernateTemplate.delete(c);
    }
    hibernateTemplate.delete(p);
    }
  }

  void create() {
    ParentPrimaryKey ppk = new ParentPrimaryKey();
    ppk.setId(new Random().nextInt());
    ppk.setVersion(new Random().nextInt());
    Parent p = new Parent();
    p.setPrimaryKey(ppk);
    hibernateTemplate.save(p);

    Child c = new Child();
    p.setChildren(Arrays.asList(c));
    c.setParentForeignKey(p);
    c.setId(new Random().nextInt());
    hibernateTemplate.save(c);
  }

  public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
    this.hibernateTemplate = hibernateTemplate;
  }

  public HibernateTemplate getHibernateTemplate() {
    return hibernateTemplate;
  }
}

18 Responses to “Hibernate Annotations for a One-To-Many Mapping Featuring a Many Side, Composite, Primary Key having a Composite, Foreign, Sub-Key”

  1. Marco says:

    In first time, thanks a lot for this post.

    I would like to know as I make a search using “criteria by example” passing a specific primary key ?

    Thank you more one time.

  2. Tarjei says:

    Thanks for the nice writeup. I used it for a simple case of one table with a composite key.

    In my experience, it is then better to use the @IdClass annotation. This may be worth knowing for people (like me) who dropped by this post and just needed composite keys.

    Thanks.

  3. Edgar says:

    Thanks so much for this post. I was working with a case of a composite-key that contained a foreign-key. No doubt it would have taken a lot longer to figure out if you hadn’t spent the time to do this write-up. Thanks for your time and the excellent details.

  4. Edgar.MS says:

    Hi, I’m other Edgar.

    I was following this example and I have a problem with the list in the parent class. The error is ” Illegal attempt to map a non collection as a @OneToMany, @ManyToMany or @CollectionOfElements “.

    Do you know this error and why I have it ??

    Thanks in advance.

  5. martykube says:

    Hi Edgar,

    What type of list do you have in the parent class? My searching has shown two cases where this exception pops up.
    1) When the collection is a user defined type
    2) When you should be using a set instead of a list

    HTH,
    Marty

  6. Edgar.MS says:

    Hi again, the error was very foolish.

    I was defined a List from org.hibernate.mapping.List instead of java.util.List, when I changed this import the example worked fine.

    Sorry & Thanks.

  7. Bryan says:

    Fantastic post, the only example of this I’ve been able to find.

    I’ve implemented this in my code, and loading by primary key, inserting, and deleting are all working great.

    When I try to get hibernate to return more than a single record however, I get a StackOverflowError.

    I’ve tried everything I can think of, any help would be greatly appreciated.

  8. abaile says:

    I had the same StackOerflowError problem. It was do to the hql pulling neer ending rows for some reason. To solve this, take the fetch=FetchType.EAGER out of the Parent Class @OneToMany annotation. This fixed it for me and was still pulling the right rows.

  9. martykube says:

    Hi Abaile,
    Thanks for the input – that makes a lot of sense. I developed this example against a small database and didn’t notice that the eager fetch sucked in the whole DB!
    Marty

  10. Harshit says:

    Hi,
    Thanks for the article.. It works and help in understanding adding foreign key …

  11. My partner and I absolutely love your blog and find almost all of your post’s to be just what I’m looking for. can you offer guest writers to write content for you personally? I wouldn’t mind writing a post or elaborating on most of the subjects you write regarding here. Again, awesome website!

  12. martykube says:

    Hi,
    Sure, I am always looking for good content. What do you have in mind?
    Marty

  13. Rakesh says:

    Hi
    I am getting below error at

    @ManyToOne
    Parent parentForeignKey;

    in ChildPrimaryKey class
    Attribute “parentForeignKey” has invalid mapping type in this context

  14. Dharmendra says:

    Thanks a lot
    This type of mapping i am searching from long time
    Its really helpful for me …

  15. Nikola says:

    hi,
    Thanks for the article ,but i have some other problem!
    situation is like this

    i have 4 tables and relation between them is this:

    table carboard:
    integer idcardboard(pk)
    .
    .
    table diagnosis
    integer iddiagnosis(pk)
    .
    .
    table review
    integer idreview(pk)
    integer idcardboard(pk,fk)
    integer iddiagnosis(fk)

    table reviewOfItems
    integer idreviewItems(pk)
    integer idreview(pk,fk)
    integer idcardboard(pk,fk)
    integer iddiagnosis(pk,fk)

    i implement tables review without problem,that works ok,but 4 table reviewOfItems makes me problem i can’t get fk iddiagnosis from table review…Error that displays Hibernate

    org.hibernate.MappingException: Unable to find column with logical name: iddiagnosis in review

    can you help me ,please…I never have this problem before…
    thanks in advance!

  16. Vishwas says:

    how can we do the same with Many-to-Many relation? i tried and i got null pointer exception
    java.lang.NullPointerException
    at org.hibernate.cfg.ColumnsBuilder.extractMetadata(ColumnsBuilder.java:141)
    at org.hibernate.cfg.AnnotationBinder.processElementAnnotations(AnnotationBinder.java:1493)
    at org.hibernate.cfg.AnnotationBinder.fillComponent(AnnotationBinder.java:2416)

  17. John Wolff says:

    Hi there,

    A little late but I’d like to thank you for this article as it describes almost exactly the problem I faced. The Oracle database was set up to mirror nor-relational mainframe records. The ‘parent’ in this case had a five-field primary key. The ‘child’ PK included the parent PK as a foreign key, plus one other field. The respective columns in the DB tables had the same names (e.g PRODUCT_CODE in both parent and child).

    I had all sorts of problems trying to get it working, but your article showed me what to do (put the parent PK into the child PK), and now everything looks to be working :)

    I didn’t need the @ManyToOne in the child, nor the associations – well, not yet anyway.

    Thanks again!
    john

Leave a Reply