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

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