There are two ways to define a composite primary key in JPA and Hibernate
Use @Embeddable class annotation along with @EmbeddedId and @MapsId field annotations
Use @IdClass class annotation
This guide will show you how to define and map a composite primary key with both approaches
Use @Embeddable, @EmbededId, and @MapsId
We can use @Embeddable class annotation to define the composite primary key class, use the @EmbeddedId field annotation to embed it to the consumer class, and use multiple @MapsId field annotations to map all keys defined in @Embeddable
Consider the many-to-many relationship between book
and publisher
tables, book_publisher
is a joined table
book_id
and publisher_id
are the composite primary key
Steps to implement follow
1) Use @Embeddable to define the composite primary key class
@Embeddable
public class BookPublisherId implements Serializable {
@Column(name = "book_id")
private Integer bookId;
@Column(name = "publisher_id")
private Integer publisherId;
...
}
2) The @Embeddable class should meet the following requirements
Implements Serializable
Implements no-arguments constructor
Implements equals and hashCode
3) Embed the composite primary key class into the consumer class with @EmbeddedId
@Table(name = "book_publisher")
@Entity
public class BookPublisher {
@EmbeddedId
private BookPublisherId id;
@ManyToOne
@MapsId("bookId")
@JoinColumn(name = "book_id")
private Book book;
@ManyToOne
@MapsId("publisherId")
@JoinColumn(name = "publisher_id")
private Publisher publisher;
@Column(name = "published_date")
private Date publishedDate;
public BookPublisher(Book book, Publisher publisher, Date publishedDate) {
this.id = new BookPublisherId(book.getId(), publisher.getId());
this.book = book;
this.publisher = publisher;
this.publishedDate = publishedDate;
}
}
4) Use @MapsId to specify the correspondent primary key defined in the @Embeddable composite primary key class
5) When creating a new instance for the joined entity, the @EmbeddedId composite primary key field should be initialized manually as Hibernate would not be able to set the value via reflection
this.id = new BookPublisherId(book.getId(), publisher.getId())
Otherwise, when saving the entity, you would get the following error in the console
Caused by: org.hibernate.PropertyAccessException: Could not set field value by reflection
Hands-on tutorials
Use @IdClass
We can define the composite primary key class and use the @IdClass class annotation to specify it on the consumer class
Consider the one-to-many relationship between employee
and employee_phone
: one employee may have multiple phone numbers
employee_id
and phone
are the composite primary key
Steps to implement follow
1) Define the composite primary key class, no class annotations would be needed
public class EmployeePhoneId implements Serializable {
private Employee employee;
private String phone;
public EmployeePhoneId() {
}
...
}
2) The composite primary key class should meet the following requirements
Field name and type have to be the same as the consumer class
Implements Serializable
Implements no-arguments constructor
Implements equals and hashCode
3) Use @IdClass to specify it on the consumer class
@IdClass(EmployeePhoneId.class)
public class EmployeePhone {
@Id
@ManyToOne
@JoinColumn
private Employee employee;
@Id
private String phone;
...
}
Hands-on tutorials
In this article, we had a quick look at two ways to map composite primary keys in JPA and Hibernate