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 thejavax.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.
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.
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.
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);
Please share your feedback on this article in the comments below. Feel free to share it with others.