Composite primary keys (Composite Identifier) typically used when mapping from legacy databases, where the primary key is comprised of several columns. In JPA, you have to make use of either of @EmbeddedId and @IdClass annotations to denote composite keys. Composite Identifiers are also known as Derived identities.

All the rules applied for a Simple Primary Key also applies for a composite primary key.

Rules for composite id (composite identifier)

There are rules that govern the usage of the composite identifier in JPA.

  • The composite primary keys must be represented by a “primary key class”. The primary key class must be defined using the javax.persistence.EmbeddedId annotation or defined using the javax.persistence.IdClass annotation.
  • The primary key class must be public and must have a public no-arg constructor.
  • The primary key class must be serializable.
  • The primary key class must define equals and `hashCode methods.

1. Composite id with @EbeddedId

To use EbeddedId, create a @Embeddable class with the properties that will be used as the composite key as shown below.

@Embeddable
public class PKStudent implements Serializable {
    private String className;
    private String rollNumber;
    private PKStudent(){}
    //Getter, setter, constructor, hashCode and equals
}

Now follow the below code, to use the PKStudent as the primary key in Student class. You need to use the @EmbeddedId annotation on top of the property to mark it as a composite id field.

@Entity
@Table(name = "my_students")
public class MyStudent {

    @EmbeddedId
    private PKStudent compId;
    private String email;
    private String name;

    //Must have empty constructor
    public MyStudent() {
    }

    public MyStudent(PKStudent compId, String email, String name) {
        this.compId = compId;
        this.email = email;
        this.name = name;
    }
    //Getter, Setter, hashCode etc 
}

You can use MyStudent just like any other class for doing CRUD operations, an example is shown below.

PKStudent pkStudent = new PKStudent("standard-I", "12345");
MyStudent myStudent = new MyStudent(pkStudent, "[email protected]", "John Doe");

//Persist into db
baseDao.save(myStudent);

//Make sure the data is persisted
MyStudent data = baseDao.find(MyStudent.class, pkStudent);

The complete code example is provided in the downloaded course material, under example03.embeddedId package.

EmbeddedId
Composite Identifier with @EmbeddedId.

1.1 EmbeddedId with @ManyToOne

NOTE: EmbeddedId class can also contain @ManyToOne annotation which is shown in the code below.

@Embeddable
public class PKSystemUser implements Serializable {

    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
    private SubSystem subSystem;

    private String username;

    public PKSystemUser() {
    }

    public PKSystemUser(SubSystem subSystem, String username) {
        this.subSystem = subSystem;
        this.username = username;
    }
    //Getter, setter etc are skipped
}

The complete code example can be found in the downloaded code, under the package example03.embeddedId.ManyToOne as shown below.

Embeddable class with ManyToOne
Embeddable class with @ManyToOne annotation.

2. Composite id with @IdClass

Another way to define the composite identifier is by using @IdClass annotation. Annotate the @Entity class with @IdClass(ClassName) and annotate the fields of Entity class with @Id. Below is the code snippet.

@Entity
@Table(name = "school_students")
@IdClass(PKSchoolStudent.class)
public class SchoolStudent {

    @Id
    private String className;

    @Id
    private String rollNumber;

    @Id
    @GeneratedValue
    private Long registrationId;

    private String email;
    private String name;

    //Must have empty constructor
    public SchoolStudent() {
    }

    public SchoolStudent(String className, String rollNumber, String email, String name) {
        this.className = className;
        this.rollNumber = rollNumber;
        this.email = email;
        this.name = name;
    }
//

PKSchoolStudent class contains the properties that are marked as id in SchoolStudent.

public class PKSchoolStudent implements Serializable {

    private String className;
    private String rollNumber;
    private Long registrationId;
    //Constructors, getter, setter, hashCode and equals method
}

Now, manipulation on the entity instance is performed as below. Basically, you need to use PKSchoolStudent as the primaryKey.

BaseDao baseDao = new BaseDao();
SchoolStudent schoolStudent = new SchoolStudent("standard-I", "12345", "[email protected]", "John Doe");

//Persist into db
baseDao.save(schoolStudent);

PKSchoolStudent pk = new PKSchoolStudent(schoolStudent.getClassName(), schoolStudent.getRollNumber(), schoolStudent.getRegistrationId());
    
//To find
SchoolStudent data = baseDao.find(SchoolStudent.class, pk);
System.out.println(data);

The complete code example is provided in the downloaded course material, under example03.IdClass package.

Composite Id class example with @IdClass

2.1 IdClass with @ManyToOne

Just like Embeddable class having @ManyToOne properties, entity annotated with @IdClass can also contain @ManyToOne annotations. This example is not included in the code, but it will look as shown below.

@Entity(name = "SystemUser")
@IdClass( PK.class )
public static class SystemUser {
    @Id
    @ManyToOne(fetch = FetchType.LAZY)
    private Subsystem subsystem;

    @Id
    private String username;

    private String name;

    //Getters, setters, constructors etc are omitted
}

Composite id with associations

JPA/Hibernate allows defining a composite identifier from entity assiciations. Below is an example of using @ManyToOne association.

@Entity
public class Book implements Serializable {

    @Id
    private String title;

    @Id
    @ManyToOne(fetch = FetchType.LAZY)
    private Author author;

    @CreationTimestamp
    private Timestamp createdOn;
    //Getter, setter, constructor etc skipped
}
@Entity
public class Author implements Serializable {

    @Id
    @GeneratedValue
    private Long id;

    private String name;
}

To save/find, you need to use the Book as the primary key.

Author author = new Author("Bikram K. Kundu");
Book book = new Book("Zero to Hero in JPA", author);

BaseDao dao = new BaseDao();
dao.save(author);
dao.save(book);

Book book1 = dao.find(Book.class, new Book(book.getTitle(), author));
System.out.println(book1);
JPA Composite id with associations

Please share your feedback on this article in the comments below. Feel free to share it with others.

By |Last Updated: September 12th, 2019|Categories: Hibernate, JPA|