Как настроить составной первичный ключ в JPA/Hibernate?

«Как настроить составной первичный ключ в JPA/Hibernate?» — вопрос из категории Hibernate, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Для использования составного (композитного) ключа в JPA необходимо создать отдельный класс-идентификатор и связать его с сущностью. Основной подход — аннотация @EmbeddedId.

Шаги настройки:

  1. Создайте класс-идентификатор (@Embeddable):

    import java.io.Serializable;
    import javax.persistence.Embeddable;
    
    @Embeddable
    public class ProjectAssignmentId implements Serializable {
        private Long projectId;
        private Long employeeId;
    
        // Обязательные: конструктор по умолчанию, equals(), hashCode()
        public ProjectAssignmentId() {}
        public ProjectAssignmentId(Long pId, Long eId) {
            this.projectId = pId;
            this.employeeId = eId;
        }
        // Геттеры, сеттеры, equals(), hashCode()...
    }
    • Класс должен быть @Embeddable.
    • Должен реализовывать Serializable.
    • Должен иметь корректные equals() и hashCode() (используйте все поля ключа).
  2. Используйте его в сущности через @EmbeddedId:

    import javax.persistence.*;
    
    @Entity
    public class ProjectAssignment {
        @EmbeddedId
        private ProjectAssignmentId id; // Сам составной ключ
    
        @ManyToOne
        @MapsId("projectId") // Связывает часть ключа с ассоциацией
        @JoinColumn(name = "project_id")
        private Project project;
    
        @ManyToOne
        @MapsId("employeeId")
        @JoinColumn(name = "employee_id")
        private Employee employee;
    
        private String role;
        // ... другие поля, конструктор, геттеры/сеттеры
    }

Альтернативный подход — @IdClass: Менее удобен, требует дублирования полей ключа и в сущности, и в отдельном классе-идентификаторе. @EmbeddedId является более современным и объектно-ориентированным выбором.