1. Введение
В этом руководстве мы узнаем о составных первичных ключах и соответствующих аннотациях в JPA.
2. Составные первичные ключи
Составной первичный ключ, также называемый составным ключом, представляет собой комбинацию двух или более столбцов для формирования первичного ключа для таблицы.
В JPA у нас есть два варианта определения составных ключей: аннотации @IdClass
и @EmbeddedId
.
Чтобы определить составные первичные ключи, мы должны следовать некоторым правилам:
- Составной класс первичного ключа должен быть общедоступным.
- Он должен иметь конструктор без аргументов.
- Он должен определить методы
equals()
иhashCode() .
- Он должен быть
сериализуемым
.
3. Аннотация IdClass
Допустим, у нас есть таблица Account
с двумя столбцами, accountNumber
и accountType,
которые образуют составной ключ. Теперь нам нужно отобразить его в JPA.
В соответствии со спецификацией JPA создадим класс AccountId
со следующими полями первичного ключа:
public class AccountId implements Serializable {
private String accountNumber;
private String accountType;
// default constructor
public AccountId(String accountNumber, String accountType) {
this.accountNumber = accountNumber;
this.accountType = accountType;
}
// equals() and hashCode()
}
Далее давайте свяжем класс AccountId
с сущностью Account
.
Для этого нам нужно аннотировать сущность аннотацией @IdClass
. Мы также должны объявить поля из класса AccountId
в сущности Account
и аннотировать их с помощью @Id
:
@Entity
@IdClass(AccountId.class)
public class Account {
@Id
private String accountNumber;
@Id
private String accountType;
// other fields, getters and setters
}
4. Аннотация EmbeddedId
@EmbeddedId
— это альтернатива аннотации @IdClass
.
Давайте рассмотрим еще один пример, где нам нужно сохранить некоторую информацию о книге
с названием
и языком
в качестве полей первичного ключа.
В этом случае класс первичного ключа BookId
должен быть аннотирован с помощью @Embeddable
:
@Embeddable
public class BookId implements Serializable {
private String title;
private String language;
// default constructor
public BookId(String title, String language) {
this.title = title;
this.language = language;
}
// getters, equals() and hashCode() methods
}
Затем нам нужно встроить этот класс в сущность Book
,
используя @EmbeddedId
:
@Entity
public class Book {
@EmbeddedId
private BookId bookId;
// constructors, other fields, getters and setters
}
5. @IdClass
против @EmbeddedId
Как мы видим, внешнее различие между ними заключается в том, что с @IdClass
нам пришлось указывать столбцы дважды, один раз в AccountId
и еще раз в Account;
однако с @EmbeddedId
мы этого не сделали.
Однако есть и другие компромиссы.
Например, эти различные структуры влияют на запросы JPQL, которые мы пишем.
С @IdClass
запрос немного проще:
SELECT account.accountNumber FROM Account account
С @EmbeddedId
нам нужно сделать один дополнительный обход:
SELECT book.bookId.title FROM Book book
Кроме того, @IdClass
может быть весьма полезен в тех случаях, когда мы используем составной ключевой класс, который мы не можем изменить.
Если мы собираемся обращаться к частям составного ключа по отдельности, мы можем использовать @IdClass,
но в местах, где мы часто используем полный идентификатор в качестве объекта, @EmbeddedId
предпочтительнее.
6. Заключение
В этой краткой статье мы рассмотрели составные первичные ключи в JPA.
Как всегда, полный код для этой статьи можно найти на Github .