Java Persistence API (JPA), версия 1.0, выпущенная в мае 2006 года как часть платформы Java EE 5, представляет собой значительный шаг вперед для архитекторов и разработчиков, желающих сохранить состояние Java в реляционных базах данных с использованием стандартных метаданных и переносимого API. Хотя стандартная спецификация ORM была несколько запоздалой, но когда она, наконец, появилась, она принесла с собой не только переносимость базы данных, которая была присуща существующим продуктам ORM, но и возможность подключить реализации различных поставщиков.
Хотя некоторые были удовлетворены проприетарными API, предлагаемыми такими продуктами, как JBoss Hibernate и Oracle TopLink, более опытные и дальновидные разработчики приложений перешли на использование JPA. Единственной проблемой было то, что JPA 1.0 не имел всех функций, которые были обнаружены в проприетарных API.
Решение, однако, было далеко от ракетостроения и заключалось в простом использовании JPA для функций, которые были там необходимы и доступны, и возвращении к проприетарному API для тех немногих функций, которые отсутствовали в стандарте. В результате для изменения базовой реализации потребовалось лишь портирование небольшого количества кода, связанного с существующей реализацией. Но это было не идеально. Экспертная группа JPA 1.0 имела лишь ограниченный период времени, в течение которого можно начинать с нуля и заканчивать полным стандартом постоянства. В качестве полезной и достижимой цели он попытался включить 80-90% функций, поддерживаемых существующими платформами ORM. Остальные должны быть добавлены в последующих выпусках.
Группа экспертов JSR 217 в настоящее время участвует в следующем этапе развития JPA. Он будет выпущен как часть Java EE 6 версии 2.0 JPA. Он также будет включать в себя большинство функций, которые люди пропустили и которые просили в JPA 1.0, такие как повышенная гибкость моделирования, дополнительные возможности объектно-реляционного сопоставления, дополнительные параметры блокировки и язык запросов на основе Java API во время выполнения. В этой серии из нескольких частей мы обсудим некоторые из этих новых функций, как они добавляются в спецификацию и как пользователи смогут их использовать.
отказ
Хотя многие из этих функций были обсуждены и близки к завершению, некоторые еще не были и еще должны обсуждаться в рамках группы. Даже те темы, на которые затихла дискуссия, не заложены в камень и могут еще измениться. Таким образом, хотя эта серия покажет вам, что будет дальше, она не дает никаких гарантий, что описанные функции находятся в окончательном виде или даже будут включены в них!
С другой стороны, нельзя также считать, что функции, описанные в этих статьях, являются полным списком нового набора функций. Члены группы регулярно вносят предложения о новых функциях, которые не были включены в повестку дня, но, тем не менее, имеют достаточную ценность, чтобы тратить усилия на уточнение.
Дополнительные возможности моделирования
В этой первой статье мы проиллюстрируем некоторые из способов, которыми JPA 2.0 увеличит гибкость, которую вы имеете как разработчик Java для моделирования ваших объектов. Это включает снятие некоторых ограничений, наложенных на модель в версии 1.0, а также предоставление доступа к дополнительным функциям и создание более естественной и самодокументируемой модели предметной области.
Гибкие режимы доступа
Иногда самые благородные причины по-прежнему приводят к жертвам (никакой политический комментарий не предназначен!), Но это было подтверждено попытками сделать JPA очень управляемой по умолчанию. Плата за попытку установить по умолчанию способ, которым разработчики JPA (обычно называемые поставщиками постоянства или просто поставщиками) осуществляют доступ к состоянию объекта, состоит в том, что фактически стало очень трудно получить исключение из значения по умолчанию, не сталкиваясь со сложностью. Результатом было то, что не было никакого способа смешать доступ к состоянию через переменные экземпляра или через методы свойств в пределах одной и той же иерархии сущностей или даже через владелец сущности и объекты, которые он мог внедрить.
Однако было найдено решение, позволяющее объекту в иерархии объявлять доступ к его состоянию иначе, чем к другим классам в иерархии. Режим доступа для класса можно установить, поместив в него аннотацию @Access, в результате чего режим доступа по умолчанию, действующий для иерархии, будет локально переопределен. Атрибут value аннотации @Access указывает FIELD или PROPERTY, чтобы определить, должен ли провайдер осуществлять доступ на основе полей или свойств.
Эта аннотация будет полезна не только для случая использования другого режима доступа для объекта в иерархии. Также будет полезно решить упомянутую выше проблему, когда у объекта есть внедренный объект, и встраиваемая структура оказывается отображенной с использованием другого типа доступа, чем у объекта-владельца. Это особенно полезно, если встраиваемый тип встроен несколькими типами сущностей с разными типами доступа. Текущая спецификация почти исключает это.
Другой случай, когда эта функция будет полезна, — это когда вам нужно выполнить какое-то простое преобразование данных при чтении или записи в базу данных. В общем случае вы хотите, чтобы поставщик обращался к данным с использованием доступа FIELD, но в этом случае вам потребуется использовать пару методов get / set для выполнения преобразования.
@Entity
@Access(FIELD)
public class Customer {
public static final LOCAL_AREA_CODE = “613”;
@Id private int id;
@Transient private String pNum;
…
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getPhoneNumber() { return pNum; }
public void setPhoneNumber(String num) { this.pNum = num; }
@Access(PROPERTY) @Column(name=”PHONE”)
protected String getPhoneNumberForDb() {
if (pNum.length() == 10)
return pNum;
else
return LOCAL_AREA_CODE + pNum;
}
protected void setPhoneNumberForDb(String num) {
if (num.startsWith(LOCAL_AREA_CODE))
pNum = num.subString(3);
else
pNum = num;
}
…
}
Из этого примера мы видим, что по сути есть три шага, чтобы сделать доступ к одному атрибуту через его свойство, а не через поле:
-
Мы помещаем аннотацию @Access (FIELD) в класс, чтобы указать, какие значения по умолчанию для класса, и включить распознавание аннотации на уровне свойств.
-
Мы помещаем аннотацию @Access (PROPERTY) в свойство, через которое мы хотим, чтобы провайдер получил доступ к состоянию.
-
Наконец, мы помечаем соответствующее поле как @Transient, чтобы режим доступа по умолчанию не пытался отобразить его в дополнение к свойству и, таким образом, дважды отображать одно и то же состояние.
Как я объяснил выше, есть и другие способы использования этой аннотации. Например, пометка встраиваемого объекта для доступа к нему определенным образом, чтобы он был правильно отображен независимо от типа объекта, которому он принадлежит. Это может быть достигнуто путем аннотирования встраиваемого объекта в режиме @Access, соответствующем сопоставлениям внутри объекта.
Производные идентификаторы
В некоторых случаях, особенно в устаревших базах данных, модель данных диктует, что первичный ключ одной таблицы включает в себя внешний ключ для другой. На рисунке ниже показано, как будет выглядеть эта модель данных в виде диаграммы, если в качестве первичного ключа проекта будет использоваться как имя проекта, так и внешний ключ для отдела.
[Img_assist | NID = 2463 | название = | убывание = | ссылка = нет | ALIGN = не определено | ширина = 329 | Высота = 127]
В терминах объекта это означает, что идентификатор объекта включает в себя один (или, возможно, более одного) из его атрибутов отношения ManyToOne или OneToOne.
Текущий способ работы с этим сценарием моделирования заключается в добавлении для каждого из отношений в идентификаторе дополнительного целочисленного атрибута @Id к объекту для представления внешнего ключа связанного объекта. Новый атрибут объекта является совершенно посторонним и избыточно сопоставляется с тем же столбцом соединения, что и отношение. Кроме того, одно из двух сопоставлений должно быть доступно только для чтения, чтобы избежать попыток вставить их в один и тот же столбец во время создания.
Было бы лучше, если бы сама связь была обозначена как часть идентификатора, избегая избыточности избыточного атрибута внешнего ключа. В JPA 2.0 таким способом можно моделировать такую сущность, добавляя аннотацию @Id (или эквивалентный элемент в дескрипторе отображения XML) в атрибут отношений.
Класс первичного ключа будет по-прежнему требоваться в обычных случаях, когда идентификатор состоит из состояния сущности и внешнего ключа отношения или если он состоит из нескольких внешних ключей. Конечно, класс первичного ключа будет включать атрибуты внешнего ключа, потому что значение необходимо для извлечения сущности. Именование атрибута класса первичного ключа также согласуется с существующими правилами, согласно которому атрибут в классе PK должен совпадать с соответствующим атрибутом в сущности, но тип атрибута в классе PK будет целочисленным, а не целевым объектом взаимосвязи. тип. Простой пример на самом деле иллюстрирует это гораздо лучше, чем мир слов.
@Entity
@IdClass(ProjectId.class)
public class Project{
@Id String name;
@Id @ManyToOne @JoinColumn(name=”D_ID”)
Department dept;
…
}
public class ProjectId {
String name;
int dept;
…
}
Удаление сироты
Агрегация сущностей или родительско-дочерние отношения не так уж редки в моделях. Хотя было возможно моделировать этот сценарий вручную, к сожалению, в JPA 1.0 не было конкретной поддержки для него. Новые атрибуты orphanRemoval теперь добавляются в аннотации отношений @OneToOne и @OneToMany, чтобы пометить отношения как отношения родитель-потомок, чтобы упростить их реализацию и сделать их более понятными для модели.
@Entity
public class Department {
@Id int id;
@OneToMany(mappedBy=”project”, orphanRemoval=true)
Collection<Project> projects;
…
}
Если, как в примере выше, для атрибута orphanRemoval установлено значение true, то при закрытии отдела его проекты также будут удалены. В целом, при включенном удалении сирот применяются два режима:
Во-первых, опция каскадного удаления автоматически применяется к отношению независимо от того, было ли оно указано в наборе каскадных операций. Если родитель удален, то ребенок также будет удален.
Во-вторых, если связь между родителем и потомком разорвана, а это означает, что указатель от родителя на потомка обнуляется, потомок будет автоматически удален. Например, если установить для родительско-дочерней коллекции значение NULL, все сущности, которые были в коллекции, будут удалены из базы данных при фиксации транзакции.
Последнее замечание об удалении сирот состоит в том, что к детям следует относиться как к частной собственности родителя. Другими словами, дочерний элемент никогда не должен принадлежать более чем одному родителю (именно поэтому он поддерживается только отношениями OneToX), и если дочерний элемент не принадлежит его родителю, он не может быть переназначен новому родителю. Новый экземпляр должен быть создан в контексте его нового родителя.
Упорядоченные списки
В настоящее время в JPA существует два распространенных способа управления упорядоченными отношениями со значениями коллекций. Во-первых, поддерживать порядок, гарантируя, что коллекция сущностей загружается в ожидаемом порядке, используя @OrderBy, и, возможно, выполняя упорядочивание в памяти, чтобы поддерживать порядок, если и когда это необходимо.
Второе — добавить атрибут «позиция» к целевому объекту, который хранится в коллекции, и сопоставить его со столбцом в целевой таблице. Этот атрибут представляет позицию объекта в Списке и должен быть текущим и синхронизированным с фактической позицией индекса объекта в коллекции. Хотя и немного обременительно, требование, чтобы столбец был явно отображен и видим в модели предметной области, как бы гармонировало с моделью данных. Тем не менее, есть те, кто не хочет, чтобы позиция просочилась в модель предметной области, поэтому JPA 2.0 предложит более удобное решение.
Версия 2.0 представит новую аннотацию под названием @OrderColumn, которая определяет столбец для использования в качестве столбца сортировки. Поставщик позаботится о том, чтобы выписать правильный столбец позиции для каждой из сущностей в коллекции, а также отсортировать коллекцию результатов запроса при их чтении.
Примечание предупреждения здесь в порядке, все же. Этот тип моделирования обычно не является лучшим подходом, и он может быть довольно дорогим с точки зрения обновлений, поскольку простое удаление первого элемента большой коллекции приведет к обновлению всех остальных объектов в коллекции или каждой записи отношения в случае отношений ManyToMany. Однако иногда люди настаивают на поддержании порядка в столбце без атрибутов, поэтому эта функция будет для них благом.
Пример использования @OrderColumn показан ниже:
@Entity
public class Department {
@Id int id;
@ManyToMany
@JoinTable(name=”DEPT_SUPP”)
@OrderColumn(name=”PREF_ORD”)
List<Supplier> preferredSuppliers;
…
}
Это отношение ManyToMany, поэтому указанный столбец заказа будет применяться к таблице соединения DEPT_SUPP, в которой хранится отношение. Если бы это было отношение OneToMany, столбец заказа был бы в таблице целевых объектов (в данном случае в таблице поставщиков).
Резюме
В этой статье вы познакомились с некоторыми функциями JPA, которые улучшат вашу способность моделировать ваши доменные объекты, включая возможность смешивать режимы доступа к сущностям, элегантно создавать идентификаторы, полученные из отношений, определять личные отношения родитель-потомок и использовать списки использования. что постоянно хранить их порядок. Хорошей новостью является то, что это была просто разминка, и это еще не все!
В следующей части мы пойдем дальше, чтобы взглянуть на недавно добавленные отображения и функциональность ORM. Увидимся снова в то же время летучей мыши, в том же месте летучей мыши.