Как разметить таблицу с составным первичным ключом в Hibernate
Что проверяют
Знаете ли вы оба способа описать составной ключ в JPA и требования к классу ключа, без которых Hibernate ведёт себя непредсказуемо. Следом обычно спрашивают, как сделать частью ключа ссылку на другую сущность и что изменится в репозитории Spring Data.
Как отвечать
Способов два:
@EmbeddedId: ключ описан классом с@Embeddable, в сущности одно поле@EmbeddedId private OrderItemId id. В JPQL к частям ключа обращаются через путь:where i.id.orderId = :id;@IdClass: в сущности остаются обычные поля с@Id, а над классом стоит@IdClass(OrderItemId.class). Имена и типы полей в классе ключа должны совпадать с полями сущности. Запросы короче, но описание ключа дублируется.
Требования к классу ключа: публичный конструктор без аргументов, implements Serializable (так требует спецификация), equals и hashCode по всем полям ключа. Persistence context ищет загруженные сущности именно по этим методам. В Hibernate 6 в роли @Embeddable можно использовать record, и методы сравнения появляются сами.
Импорты сейчас из jakarta.persistence: пакет javax.persistence остался в Hibernate 5 и Spring Boot 2.
Частый реальный случай: ключ строки заказа состоит из order_id и product_id, и обе части это внешние ключи. Тогда рядом с @EmbeddedId объявляют @ManyToOne @MapsId("orderId") private Order order;, и Hibernate берёт значение части ключа из связи, не дублируя колонку.
В Spring Data репозиторий объявляется как JpaRepository<OrderItem, OrderItemId>, а findById принимает экземпляр класса ключа.
Где спотыкаются
Оставляют equals и hashCode унаследованными от Object. Сессия не узнаёт уже загруженную сущность по новому экземпляру ключа, и в одной транзакции появляются дубликаты или исключения при слиянии.
Делают поля ключа изменяемыми и меняют их у загруженной сущности. Первичный ключ в JPA менять нельзя: нужно удалить строку и создать новую.
Пишут javax.persistence в проекте на Spring Boot 3 и новее. Код не компилируется, а кандидат выглядит так, будто последний раз работал с JPA несколько лет назад.