Во второй части этой серии кратких обзоров на JPA 2.0 я выполнил свое обещание немного рассказать вам о некоторых новых дополнениях к набору объектно-реляционных отображений, стандартизированных в JPA. Тем не менее, я все еще должен сохранить свой отказ от ответственности из первой статьи (см. « Ожидание JPA 2.0 — часть 1 ») и сказать, что спецификация не является окончательной, поэтому ни одно из того, что включено в эту статью, не было полностью переработано.
Новые функции объектно-реляционного сопоставления делятся на две основные категории:
а) расширяют возможности моделирования, которые можно сопоставить;
б) предлагают возможность более гибкого отображения существующих моделей.
В этот момент возвращающийся читатель может почувствовать себя немного смущенным, заметив, что первая категория довольно близка к теме последней статьи. По правде говоря, это на самом деле накладывается, причина в большей степени связана с тем, что в последней статье было слишком много вещей, о которых нужно упомянуть, и недостаточно места для этого, а также с тем, что эти темы и сформировать хороший переход от одного к другому. Но я отвлекся. Давайте перейдем к функциям.
Дополнительные параметры модели на карте
Некоторые из ограничений на то, что вы можете поместить в свой доменный объект, связаны главным образом с временными ограничениями, существовавшими во время создания первой версии спецификации. Некоторые из наиболее часто запрашиваемых отсутствующих отображений — это наборы базовых типов, наборы встроенных объектов, встроенные отношения с другими объектами и расширенная поддержка Map.
Коллекции базовых и встраиваемых объектов
Коллекции вещей, очевидно, чрезвычайно распространены, и хотя в JPA 1.0 возможно иметь коллекции сущностей, неудивительно, что люди могут также захотеть сохранить коллекции не-сущностей, таких как базовые объекты или даже встраиваемые объекты. Чтобы учесть эти два новых случая, была добавлена пара новых аннотаций, в настоящее время называемых @ElementCollection и @CollectionTable. Аннотация @ElementCollection используется для указания того, что объекты в коллекции хранятся в таблице коллекции, а аннотация @CollectionTable позволяет указывать детали таблицы коллекции. Если явно не включено, то будут применяться значения таблицы сбора по умолчанию.
Давайте используем пример Транспортного средства, которое оснащено набором возможных дополнительных функций, где каждая функция является перечисляемым значением типа FeatureType. Сервисное посещение — это встроенный объект, в котором хранится дата посещения, стоимость и описание проделанной работы. Основные перечисляемые FeatureType и встраиваемые типы ServiceVisit выглядят следующим образом:
public enum FeatureType { AC, CRUISE, PWR, BLUETOOTH, TV /* … */ } @Embeddable public class ServiceVisit { @Temporal(DATE) @Column(name=”SVC_DATE”) Date serviceDate; String workDesc; int cost; }
Теперь мы можем использовать эти типы в нашей сущности «Автомобиль», которая может иметь ноль или более дополнительных функций, а также историю обслуживания (список всех посещений службы).
@Entity public class Vehicle { @Id int vin; @ElementCollection @CollectionTable(name=”VEH_OPTNS”) @Column(name=”FEAT”) Set<FeatureType> optionalFeatures; @ElementCollection @CollectionTable(name=”VEH_SVC”) @OrderBy(“serviceDate”) List<ServiceVisit> serviceHistory; … }
Модель данных будет выглядеть так:
[Img_assist | NID = 3257 | название = | убывание = | ссылка = нет | ALIGN = не определено | ширина = 330 | высота = 171]
Аннотация @Column в сопоставлении optionFeatures относится к столбцу, в котором хранится значение типа объекта, и, как мы видим в модели данных, он будет находиться в таблице коллекции VEH_OPTNS, а не в таблице VEHICLE.
Встроенные отношения
Иногда объект имеет отношение к другому объекту, но на самом деле это отношение более удобно хранить как часть встроенного объекта в исходном объекте. Раньше отношения не могли содержаться в встраиваемых объектах, но в JPA 2.0 это ограничение было снято.
Возвращаясь к нашему примеру с транспортным средством, мы можем добавить встроенный объект PurchaseInfo, который содержит такую информацию, как цена покупки и дата продажи, а также ссылку на дилера, который продал автомобиль. Наш объект PurchaseInfo может выглядеть следующим образом:
@Embeddable public class PurchaseInfo { int price; @Temporal(DATE) @Column(name=”PUR_DATE”) Date purchaseDate; @ManyToOne Dealer dealer; }
Отношение @ManyToOne, которое существует в PurchaseInfo, на самом деле является просто отношением между Транспортным средством и Дилером, но так получилось, что это отношение определено в объекте PurchaseInfo, который встроен в Транспортное средство.
Эти отношения могут быть однонаправленными, но мы могли бы также легко сделать их двунаправленными, отобразив их на стороне Дилера. Если бы он был двунаправленным, то Дилер ссылался бы на целевую сущность как Транспортное средство и указывал на атрибут дилера через атрибут purchaseInfo Транспортного средства. Соответствующий код автомобиля и дилера указан ниже.
@Entity public class Vehicle { @Id int vin; @Embedded PurchaseInfo purchaseInfo; … } @Entity public class Dealer { @Id int dealerId; @OneToMany(mappedBy=”purchaseInfo.dealer”) Collection<Vehicle> vehiclesSold; … }
Карты и больше карт
В настоящее время карты могут использоваться для хранения сущностей в отношениях «один ко многим» и «многие ко многим», но ключи должны быть атрибутами первичного ключа или другими уникальными атрибутами целевых объектов. Было бы неплохо иметь возможность хранить сущности с ключами базового типа или просто использовать карту для хранения базовых типизированных значений.
Карты теперь могут содержать любую комбинацию основных типов, встраиваемых объектов и объектов в качестве ключей или значений. Это обеспечивает значительную гибкость и открывает двери для использования почти полностью произвольно набранных карт в модели сущностей. Из-за нехватки места я не смог показать примеры всех возможных комбинаций, но я возьму пару конфигураций и продемонстрирую, как их можно использовать, и надеюсь, что вы поймете основную идею.
В нашем предыдущем примере кода наш объект PurchaseInfo, встроенный в Vehicle, содержал отношение «многие к одному» с Dealer, а Dealer имел отношение «один ко многим» обратно к Vehicle. Если бы мы на самом деле хотели увидеть информацию о покупке со стороны Дилера, то было бы разумно иметь карту проданных автомобилей с ключом PurchaseInfo. Чтобы достичь этого, наш дилерский объект должен выглядеть так:
@Entity public class Dealer { @Id int dealerId; @OneToMany(mappedBy=”purchaseInfo.dealer”) Map<PurchaseInfo,Vehicle> vehiclesSold; @ElementCollection @CollectionTable(name=”VEH_INV”, joinColumns=@JoinColumn(name=”DLR_ID”)) @MapKeyJoinColumn(name=”V_ID”) @Column(name=”COUNT”) Map<Vehicle,Integer> inventory; … }
В двунаправленной целевой связи внешнего ключа «один ко многим» ключ этой связи сохраняется в целевой таблице. В нашем случае ключ — это просто внедренный объект (в целевой таблице), поэтому сопоставленные значения встраиваемого класса применяются для получения объекта PurchaseInfo из таблицы VEHICLE. В качестве предупреждения следует отметить, что объект PurchaseInfo, встроенный в Vehicle, не обязательно идентичен ключевому экземпляру на карте VehiclesSold. Они не являются объектами первого класса и, следовательно, не имеют права на уникальное управление их идентичностью.
Вторая коллекция — это карта, которая моделирует инвентарь транспортного средства, отслеживая количество каждого типа транспортного средства на складе. Ключом является сущность Vehicle, а значением является простое целое число, поэтому это коллекция элементов, которая хранится в таблице коллекций.
Аннотации @MapKeyJoinColumn и @Column обозначают столбцы, используемые для хранения ключа и значения для каждой записи карты и применения к таблице сбора. Аннотация @MapKeyJoinColumn используется только тогда, когда ключ Map является сущностью и указывает столбец внешнего ключа, ссылающийся на первичную таблицу этой сущности. Аннотация @Column указывает на столбец, содержащий значение Map, и в этом случае мы сохраняем значение инвентаря в столбце «COUNT». Принимая во внимание вышеприведенные аннотации, мы указываем таблицу сбора, которая выглядит следующим образом:
Картирование моделей более гибко
Бывают случаи, когда идеальная объектная модель должна отображаться в ранее существовавшую модель данных, которая не полностью соответствует отображениям, доступным в JPA 1.0. Гибкость использования альтернативных моделей данных добавляет мощности и применимости к API.
Унарное соединение
Однонаправленное отношение «многие к одному» или «один к одному» почти всегда моделируется внешним ключом в первичной таблице исходного объекта. В случае двунаправленной связи «один к одному» она, конечно, может находиться в первичной таблице целевой сущности, если смотреть на нее с другой стороны отношения. В редких случаях для хранения внешнего ключа может использоваться отдельная таблица соединений. В результате для этой цели было необходимо разрешить назначение отдельной таблицы соединения и не требовать, чтобы она была в таблице того или другого объекта. Давайте вернемся к нашему Транспортному средству и попробуем это.
У нас может быть таблица, которая связывает транспортное средство с лицом, на которое зарегистрировано транспортное средство. Если добавить отношение «один к одному» между Vehicle и Owner и указать аннотацию @JoinTable в дополнение к аннотации @OneToOne в отношении владельца, тогда будут применяться значения таблицы соединения по умолчанию.
Однонаправленный внешний ключ «один ко многим»
Наиболее разумный и распространенный способ моделирования отношения «один ко многим / многие к одному» заключается в том, чтобы отношения были двунаправленными и хранили внешний ключ на стороне «многие к одному». Однако в некоторых неясных случаях некоторые просят о возможности иметь однонаправленное отношение «один ко многим», но держать внешний ключ на стороне «многие к одному». Это было бы эквивалентно, скажем, наличию отношения «один ко многим» от «Транспортного средства» к его частям, но не иметь никакого отношения от «Части» к «Транспортному средству», даже если в таблице PART существует столбец внешнего ключа. Когда деталь добавляется в список деталей для транспортного средства, строка, соответствующая этой части в таблице PART, должна обновляться, даже если объект Part ничего не знает о транспортном средстве, частью которого он является.
Проблема сводится к тому, что есть метаданные отношения (внешний ключ), сохраняемые с экземплярами Part, даже если сущность Part на самом деле не участвует в этих отношениях. Тем не менее, отображение было добавлено, и таблица соединений, которая обычно требуется для однонаправленных отображений «один ко многим», теперь является необязательной. Способ идентифицировать сопоставление, такое как описываемое нами, состоит в том, чтобы аннотировать отношения с помощью аннотации @JoinColumn. Если атрибут имени @JoinColumn опущен, применяются правила столбца соединения по умолчанию.
@Entity public class Vehicle { @Id int vin; @OneToMany @JoinColumn(name=”V_ID”) Collection<Part> parts; … }
Отсутствие атрибута mappedBy в аннотации @OneToMany указывает на то, что сущность Vehicle владеет отношением, т. Е. Является однонаправленной, а наличие @JoinColumn указывает на то, что внешний ключ находится в целевой таблице PART, а не в таблице соединения.
Резюме
Теперь вы являетесь экспертами в объектно-реляционных аспектах отображений JPA 2.0 и можете произвести впечатление на своих друзей, сказав что-то вроде: «Я не могу дождаться выхода JPA 2.0, когда я смогу использовать таблицу сбора для хранения своей карты основных объектов под ключ встраиваемых ». В этот момент вы просто уходите и притворяетесь, что то, о чем вы говорили, было настолько невероятно ценным, что человеку пришлось бы сходить с ума, чтобы не использовать его.
В следующий раз мы рассмотрим усовершенствования запросов и способы использования выражений в JPA для создания динамических объектно-ориентированных запросов. Оставайтесь в курсе!
Для дальнейшей справочной информации прочитайте часть 1 этой серии статей .