Что такое составной первичный ключ в БД и когда он применяется?

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

Ответ

Составной первичный ключ (Composite Primary Key) — это первичный ключ, состоящий из двух или более столбцов таблицы. Уникальность записи обеспечивается только комбинацией значений этих столбцов.

Когда применяется:

  1. В связующих (junction) таблицах для отношений "многие-ко-многим".
  2. Когда бизнес-логика определяет сущность уникальной парой/набором атрибутов (например, номер рейса + дата вылета).

Пример на Java с JPA/Hibernate:

// 1. Класс-идентификатор (должен быть Serializable и реализовывать equals/hashCode)
public class EnrollmentId implements Serializable {
    private Long studentId;
    private Long courseId;

    // Конструкторы, геттеры, сеттеры...

    @Override
    public boolean equals(Object o) { /* сравнение по studentId и courseId */ }
    @Override
    public int hashCode() { /* хэш-код на основе studentId и courseId */ }
}

// 2. Сущность, использующая составной ключ через аннотацию @IdClass
@Entity
@IdClass(EnrollmentId.class) // Указываем класс-идентификатор
public class Enrollment {
    @Id
    @Column(name = "student_id")
    private Long studentId;

    @Id
    @Column(name = "course_id")
    private Long courseId;

    private Integer grade;
    private LocalDate enrolledDate;

    // Связи с сущностями Student и Course (опционально)
    @ManyToOne
    @MapsId("studentId") // Связывает с частью ключа
    @JoinColumn(name = "student_id", insertable = false, updatable = false)
    private Student student;

    @ManyToOne
    @MapsId("courseId")
    @JoinColumn(name = "course_id", insertable = false, updatable = false)
    private Course course;
    // ...
}

Как это работает в SQL:

CREATE TABLE enrollment (
    student_id BIGINT NOT NULL,
    course_id BIGINT NOT NULL,
    grade INT,
    enrolled_date DATE,
    PRIMARY KEY (student_id, course_id), -- Составной ключ
    FOREIGN KEY (student_id) REFERENCES student(id),
    FOREIGN KEY (course_id) REFERENCES course(id)
);

Почему это важно: Гарантирует, что один студент может быть записан на конкретный курс только один раз. Альтернативный подход — использование суррогатного ключа (отдельный id) и уникального ограничения на пару (student_id, course_id).