Как смапить составной ключ

Составной ключ состоит из нескольких атрибутов. В JPA есть два основных способа: @EmbeddedId и @IdClass.

Класс ключа должен корректно реализовывать equals и hashCode, быть сериализуемым по требованиям используемой версии JPA и иметь подходящий конструктор.

@EmbeddedId

@Embeddable
public class EnrollmentId implements Serializable {
    private Long studentId;
    private Long courseId;
 
    // equals и hashCode по обоим полям
}
 
@Entity
public class Enrollment {
    @EmbeddedId
    private EnrollmentId id;
}

Ключ представлен одним value object. Этот вариант обычно лучше группирует связанные поля.

@IdClass

public class EnrollmentId implements Serializable {
    private Long studentId;
    private Long courseId;
}
 
@Entity
@IdClass(EnrollmentId.class)
public class Enrollment {
    @Id
    private Long studentId;
 
    @Id
    private Long courseId;
}

Имена и типы полей класса ключа должны соответствовать полям сущности.

Связь как часть ключа

С @EmbeddedId удобно использовать @MapsId, чтобы один и тот же столбец был и внешним, и первичным ключом:

@MapsId("studentId")
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "student_id")
private Student student;

Когда лучше surrogate key

Если естественный ключ велик, меняется со временем или проходит через много внешних ключей, технический Long/UUID упрощает связи. Естественную уникальность при этом сохраняют отдельным UNIQUE.

Вопросы для повторения

  1. Чем EmbeddedId отличается от IdClass? В первом ключ — поле-value object, во втором его атрибуты повторены непосредственно в сущности.
  2. Почему важны equals и hashCode? Ключ участвует в идентичности сущности и коллекциях.
  3. Можно ли менять первичный ключ managed-сущности? Не следует; идентичность сущности должна быть стабильной.
  4. Что делает @MapsId? Связывает атрибут отношения с частью embedded-ключа.
  5. Всегда ли естественный ключ лучше? Нет, изменяемость и ширина могут сделать surrogate key практичнее.

Связано: Встраиваемый класс Embeddable, JoinColumn JoinTable, Аннотация GeneratedValue.