Например, приложению CD-BookStore необходимо часто выкладывать информацию на главной странице, где вы сможете читать ежедневные новости о книгах, музыке или артистах. У новостей будет иметься содержимое, название и, поскольку они могут быть написаны на нескольких языках, код языка (EN для английского, PT — для португальского и т. д.). Первичным ключом для новостей тогда сможет быть название и код языка, поскольку статья может быть переведена на разные языки, но с сохранением ее оригинального названия. Таким образом, класс первичного ключа NewsId будет включать два атрибута, имеющие тип String: title и language. Классы первичных ключей должны включать определения методов для equals() и hashCode() для управления запросами и внутренними коллекциями (равенство в случае с этими методами должно быть таким же, как и равенство, которое имеет место в случае с базой данных), а их атрибуты должны быть допустимых типов, входящих в приведенный ранее набор. Они также должны быть открытыми, реализовывать интерфейс Serializable, если им потребуется пересекать архитектурные уровни (например, управление ими будет осуществляться на постоянном уровне, а использование — на уровне представления), и располагать конструктором без аргументов.
@EmbeddedId. Как вы еще увидите позднее в этой главе, JPA использует встроенные объекты разных типов. Резюмируя, отмечу, что у встроенного объекта нет какого-либо идентификатора (собственного первичного ключа), а его атрибуты в итоге окажутся столбцами таблицы содержащей их сущности.
В листинге 5.5 класс NewsId показан как встраиваемый. Он представляет собой всего лишь встроенный объект (аннотированный с использованием @Embeddable), который в данном случае включает два атрибута (title и language). У этого класса должен быть конструктор без аргументов, геттер, сеттер, а также реализации equals() и hashCode(). Это означает, что он должен придерживаться соглашений JavaBeans. У этого класса как такового нет собственного идентификатора (отсутствует аннотация @Id). Это характерно для встраиваемых классов.
@Embeddable
public class NewsId {
··private String title;
··private String language;
··// Конструкторы, геттеры, сеттеры
}
В случае с сущностью News, показанной в листинге 5.6, затем придется встроить класс первичного ключа NewsId с применением аннотации @EmbeddedId. Благодаря такому подходу не потребуется использовать @Id. Каждая аннотация @EmbeddedId должна ссылаться на встраиваемый класс, помеченный @Embeddable.
@Entity
public class News {
··@EmbeddedId
··private NewsId id;
··private String content;
··// Конструкторы, геттеры, сеттеры
}
В следующей главе я опишу, как находить сущности с использованием их первичного ключа. Вот первый, но мимолетный взгляд на то, как это происходит: первичный ключ — это класс с конструктором. Вам придется создать экземпляр этого класса со значениями, формирующими ваш уникальный ключ, и передать соответствующий объект менеджера сущностей (атрибут em), как показано в этом коде:
NewsId pk = new NewsId("Richard Wright has died on September 2008", "EN")
News news = em.find(News.class, pk);
@IdClass. Другой метод объявления составного ключа — использование аннотации @IdClass. Это иной подход, в соответствии с которым каждый атрибут класса первичного ключа также должен быть объявлен в классе-сущности и аннотирован с помощью @Id.
Составной первичный ключ в примере NewsId, приведенном в листинге 5.7, является всего лишь POJO, которому не требуется какая-либо аннотация (в предыдущем примере в листинге 5.6 класс первичного ключа приходилось аннотировать с помощью @EmbeddedId).
public class NewsId {
private String title;
··private String language;
··// Конструкторы, геттеры, сеттеры
}
Затем для сущности News, показанной в листинге 5.8, придется определить первичный ключ с использованием аннотации @IdClass, и аннотировать каждый ключ посредством @Id. Для обеспечения постоянства сущности News вам потребуется задать значения для атрибутов title и language.
@Entity
@IdClass(NewsId.class)
public class News {