- •Обозначения и сокращения
- •введение
- •1. Установка и настройка инструментальных средств
- •1.1. Установка и подготовка к работе операционной системы
- •1.2. Установка программного обеспечения
- •1.3. Создание таблиц в базе данных
- •2. Основы Java EE 6
- •2.1. Распределенные многоуровневые приложения
- •2.2. Контейнеры Java EE
- •2.3. Сервер GlassFish v3
- •2.4. Структура приложения
- •2.5. Конфигурирование приложений
- •2.6. Задание ссылок на ресурсы
- •4. Введение в компоненты Facelets
- •4.1. Веб-страницы
- •4.2. Разработка простого приложения Facelets
- •4.3. Использование шаблонов
- •5. Унифицированный язык записи выражений
- •6.1. Добавление компонент библиотеки HTML на страницу
- •6.2. Использование компонент для таблиц баз данных
- •6.3. Использование тегов библиотеки Core
- •7. Использование конвертеров, слушателей и проверок
- •7.1. Использование стандартных преобразователей
- •7.2. Регистрация слушателей для компонентов
- •8. Внешние объекты (JavaBeans)
- •8.1. Создание класса внешних объектов
- •8.2. Описание свойств бинов
- •8.3. Написание методов внешних бинов
- •8.4. Проверка бинами
- •9.1. Файл конфигурации ресурсов приложения
- •9.2. Упорядочение ресурсов конфигурации приложения
- •9.3. Конфигурирование состояния проекта
- •9.4. Выбор конфигурации бина
- •9.5. Регистрация сообщений об ошибках как пакет ресурса
- •9.7. Конфигурирование правил навигации (Navigation Rules)
- •9.8. Основные требования приложения JavaServer Faces
- •10. Технология Java Servlet
- •11. Введение в Java Persistence API
- •11.1. Требования к классам сущностей
- •11.3. Внедряемые классы в сущностях
- •11.4. Наследование сущностей
- •11.5. Стратегии наследования сущностей с отображением
- •11.6. Управление сущностями
- •11.7. Запросы сущностей
- •12. Примеры хранимых сущностей
- •12.1. Приложение order
- •12.2. Пример получения схемы отношений на основе таблиц БД
- •13.1. Терминология языка запросов
- •13.3. Упрощенный синтаксис языка запросов
- •13.4. Примеры запросов
- •13.5. Запросы с навигацией связанных сущностей
- •13.6. Запросы с другими условными выражениями
- •13.7. Изменение и удаление группы сущностей
- •13.8. Полный синтаксис языка запросов
- •14. Язык запросов Criteria API
- •14.3. Корни запроса
- •14.4. Использование объединения в запросе
- •14.5. Навигация путей в запросах
- •14.6. Ограничения на результаты запроса
- •14.7. Управление результатами запросов
- •14.8. Исполнение запросов
- •15. Связывание ресурсов
- •15.1. Ресурсы и служба имен JNDI
- •15.2. Объекты DataSource и пулы соединений (Connection Pools)
- •15.3. Внедрение ресурсов
- •15.4. Адаптеры ресурсов
- •15.5. Аннотации метаданных
- •16. Безопасность веб-приложений
- •16.1. Краткий обзор
- •16.2. Механизмы обеспечения безопасности
- •16.3. Безопасность сервера предприятия
- •16.4. Использование защищенного соединения SSL
- •18. Пример приложения
- •18.1. Создание проекта веб-приложения
- •18.3.Структура приложения JavaEE 6
- •18.4. Программирование вида для объектов
- •18.5. Дизайн главной страницы
- •18.6. Страница просмотра записей таблицы городов
- •18.7. Страница добавления записей о городах
- •18.8. Страница редактирования записей о городах
- •18.9. Страница удаления записей о городах
- •19. Обработка связей внешних ключей
- •19.1. Разработка класса для вида сущности
- •19.2. Доработка вида для городов
- •19.3. Разработка обзорной страницы
- •19.5. Страница для редактирования записей с внешними ключами
- •20. Дополнительные функции
- •20.1. Сортировка записей таблицы
- •20.2. Контроль за удалением связанных записей
- •20.3. Контроль ввода наименований
- •20.4. Запросы к БД на языке Java Persistence Query Language
- •20.5. Управление страницами при просмотре таблицы
- •20.6. Создание и просмотр отчетов
- •20.7. Использование шаблонов и стилей
- •20.8. Защита приложения паролем
- •Заключение
- •Библиографический список
12. Примеры хранимых сущностей
Эта глава описывает использование языка Java Persistence API на примере
приложения. Особое внимание уделено исходному коду и установкам параметров. Приведённый пример является приложением с именем order, которое использует се-
ансовый бин stateful, чтобы управлять объектами, имеющими отношение к системе
заказов.
12.1. Приложение order
Приложение order — простое приложение для поддержки каталога запчастей и
размещения заказа на эти запчасти. У него есть сущности, которые представляют зап-
части, поставщиков, заказы и пункты заказа. Эти сущности использует сеансовый бин с
хранимым состоянием (stateful), который содержит бизнес-логику приложения. Простой одиночный сеансовый бин создает начальные сущности при развертывании приложе-
ния. Приложение Facelets манипулирует данными и отображает их из каталога.
Информация в заказе может быть подразделена на разные элементы:
•Что такое номер заказа?
•Какие запчасти включены в заказ?
•Какие запчасти составляют эту запчасть?
•Кто сделал запчасть?
•Что есть спецификация для запчасти?
•Есть ли схема для запчасти?
Приложение order является упрощенной версией системы заказов, которая включает все эти элементы.
Приложение order состоит из одного модуля WAR, который включает классы бинов, сущности, поддержку классов, Facelet, файлы XHTML и классов.
12.1.1. Отношения сущностей в приложении order
Приложение order демонстрирует несколько типов связей сущностей: «один-
ко-многим», «многие-к-одному», «один-к-одному», однонаправленные и самоссылающиеся связи.
Самоссылающиеся связи
Самоссылающееся отношение является отношением между полями отношения
для той же сущности. Запчасть имеет поле bomPart, которая имеет отношение «один- ко-многим» с полем запчастей parts в сущности Part. То есть, запчасть может состоять
из многих запчастей, и каждая из этих запчастей имеет точно один материал.
Первичный ключ для сущности Part — составной: комбинация полей partNumber и revision. Он отображается в столбцы PARTNUMBER и REVISION в таблице EJB_
ORDER_PART.
...
@ManyToOne
@JoinColumns({
@JoinColumn(name="BOMPARTNUMBER",
referencedColumnName="PARTNUMBER"),
@JoinColumn(name="BOMREVISION",
129
referencedColumnName="REVISION")
})
public Part getBomPart() { return bomPart;
}
...
@OneToMany(mappedBy="bomPart") public Collection<Part> getParts() {
return parts;
}
...
Отношение «один-к-одному» (One-to-One)
Сущность Part имеет поле vendorPart, которое имеет взаимно однозначную
связь с полем VendorPart сущности Part.
То есть каждая запчасть имеет точно одного поставщика, и наоборот. Вот отношение, отображаемое в Part:
@OneToOne(mappedBy="part")
public VendorPart getVendorPart() { return vendorPart;
}
Отношение, отображаемое в VendorPart:
@OneToOne
@JoinColumns({
@JoinColumn(name="PARTNUMBER",
referencedColumnName="PARTNUMBER"),
@JoinColumn(name="PARTREVISION",
referencedColumnName="REVISION")
})
public Part getPart() { return part;
}
Поскольку Part использует составной первичный ключ, использована аннотация @JoinColumns, чтобы отображать столбцы в таблице PERSISTENCE_ORDER_
VENDOR_PART в столбцы в PERSISTENCE_ORDER_PART. Столбец PARTREVISION таблицы PERSISTENCE_ORDER_VENDOR_PART ссылается на столбец REVISION
таблицы PERSISTENCE_ORDER_PART.
Отношение «один-ко-многим» (One-to-Many), отображенное в перекрытие первичного и внешнего ключей
Заказ имеет поле lineItems, которое имеет связь «один-ко-многим» с полем заказов order таблицы LineItem. То есть каждый заказ имеет один или более пунктов.
LineItem использует составной первичный ключ из полей orderId и itemId. Этот
составной первичный ключ отображается в столбцы ORDERID и ITEMID таблицы
PERSISTENCE_ORDER_LINEITEM в базе данных. ORDERID — внешний ключ, ото-
бражается в столбец ORDERID в таблице PERSISTENCE_ORDER_ORDER. Это означает, что столбец ORDERID отображается дважды:
•первый раз как поле первичного ключа orderId;
•второй раз как поле связи order-отношения.
130
Здесь показано отображение связи в класс Order:
@OneToMany(cascade=ALL, mappedBy="order") public Collection<LineItem> getLineItems() { return lineItems;
}
Вот отношение, отображающееся в класс LineItem:
@ManyToOne
public Order getOrder() { return order;
}
Однонаправленная связь
Сущность LineItem имеет поле vendorPart, которое имеет однонаправленную связь «многие-к-одному» с сущностью VendorPart. То есть в этом отношении нет поля
в целевом объекте.
@ManyToOne
public VendorPart getVendorPart() { return vendorPart;
}
12.1.2. Первичный ключ в приложении order
Приложение order использует несколько типов первичных ключей: однозначные первичные ключи, составные первичные ключи и сгенерированные первичные ключи.
Сгенерированные первичные ключи
СущностиVendorPart используютзначениясгенерированныхпервичныхключей.
То есть приложение не назначает значений первичного ключа для сущностей, а дове-
ряет провайдеру хранилища генерировать значения первичного ключа. Употребляется аннотация @GeneratedValue для указания того, что объект использует сгенерированный первичный ключ.
В классе VendorPart следующий код определяет установку параметров для ге-
нерации значений первичного ключа:
@TableGenerator(
name="vendorPartGen", table="PERSISTENCE_ORDER_SEQUENCE_GENERATOR", pkColumnName="GEN_KEY", valueColumnName="GEN_VALUE", pkColumnValue="VENDOR_PART_ID", allocationSize=10)
@Id
@GeneratedValue(strategy=GenerationType.TABLE,
generator="vendorPartGen")
public Long getVendorPartNumber() { return vendorPartNumber;
}
Аннотация @TableGenerator используется вместе с элементом strategy=TABLE
в @GeneratedValue и задаёт стратегию для генерации первичного ключа в таблице
131
базы данных. Аннотация @TableGenerator использована, чтобы задать генератор для таблицы. Элемент name задаёт имя vendorPartGen для генератора, которое есть в
классе VendorPart.
Таблица EJB_ORDER_SEQUENCE_GENERATOR, которая имеет два столб-
ца GEN_KEY и GEN_VALUE, загрузит генерированные значения первичного ключа.
Эта таблица может быть использована, чтобы генерировать другие первичные ключи. Поэтому элемент pkColumnValue установлен в VENDOR_PART_ID, чтобы различать
эти генерированные первичные ключи от других генерированных первичных ключей.
Элемент allocationSize определяет приращение при создании значений первичных
ключей. В данном случае каждый следующий первичный ключ сущности VendorPart
увеличится на 10.
Поле первичного ключа vendorPartNumber — типа Long, так как сгенерирован-
ный первичный ключ должен быть этого типа.
Составные первичные ключи
Составные первичные ключи состоят из многих полей. Чтобы использовать со-
ставной первичный ключ, вы должны создать обертывающий класс.
Вприложении order две сущности используют сложные первичные ключи: Part
иLineItem.
Part использует класс суперобложки PartKey. Первичный ключ Part — комбина-
ция индекса и номера издания. PartKey инкапсулирует этот первичный ключ.
LineItem использует класс LineItemKey. Первичный ключ LineItem — комбинация номера заказа и номера пункта. LineItemKey инкапсулирует этот первичный ключ. Это — класс суперобложки LineItemKey сложного первичного ключа:
package order.entity;
public final class LineItemKey implements java.io.Serializable {
private Integer orderId; private int itemId;
public int hashCode() {
return ((this.getOrderId()==null ? 0:this.getOrderId().hashCode())
^ ((int) this.getItemId()));
}
public boolean equals(Object otherOb) { if (this == otherOb) {
return true;
}
if (!(otherOb instanceof LineItemKey)) { return false;
}
LineItemKey other = (LineItemKey) otherOb; return ((this.getOrderId()==null ?
other.orderId==null : this.getOrderId().equals (other.orderId)) &&
(this.getItemId == other.itemId));
}
public String toString() {
132
return "" + orderId + "-" + itemId;
}
}
Аннотация @IdClass использована для определения класса первичного ключа в классе сущности. В LineItem @IdClass используется следующим образом:
@IdClass(order.entity.LineItemKey.class)
@Entity
...
public class LineItem {
...
}
Два поля в LineItem с аннотаций @Id нужны, чтобы выделить эти поля как часть
составного первичного ключа:
@Id
public int getItemId() { return itemId;
}
...
@Id
@Column(name="ORDERID", nullable=false, insertable=false, updatable=false)
public Integer getOrderId() { return orderId;
}
Для orderId вы также используете аннотацию @Column, чтобы определить имя столбца в таблице. Этот столбец не должен включаться или изменяться, так как
это перекрывающий внешний ключ, указывающий на столбец ORDERID таблицы
PERSISTENCE_ORDER_ORDER (см. отображение отношения «один-ко-многим» для перекрытия первичного и внешнего ключа выше). То есть orderId будет установлен сущностью Order.
В конструкторе LineItem номер пункта (LineItem.itemId) будет установлен мето-
дом Order.getNextId.
public LineItem(Order order, int quantity, VendorPart vendorPart) {
this.order = order;
this.itemId = order.getNextId(); this.orderId = order.getOrderId(); this.quantity = quantity; this.vendorPart = vendorPart;
}
Order.getNextId считает количество текущих пунктов, добавляет единицу и воз-
вращает число.
public int getNextId() {
return this.lineItems.size() + 1;
}
Сущность Part не требует аннотации @Column в двух полях, которые включаются в сложный первичный ключ. Дело в том, что сложный первичный ключ сущности Part не является перекрывающим первичным/внешним ключом.
133
@IdClass(order.entity.PartKey.class)
@Entity
...
public class Part {
...
@Id
public String getPartNumber() { return partNumber;
}
...
@Id
public int getRevision() { return revision;
}
...
}
Отображение сущностей в несколько таблиц базы данных
Поля сущности Part отображаются в более чем одной таблице базы данных:
PERSISTENCE_ORDER_PART и PERSISTENCE_ORDER_PART_DETAIL. Таблица
PERSISTENCE_ORDER_PART_DETAIL содержит спецификацию и схемы для части. Аннотация @SecondaryTable используется для определения второстепенной таблицы.
...
@Entity @Table(name="PERSISTENCE_ORDER_PART")
@SecondaryTable(name="PERSISTENCE_ORDER_PART_DETAIL", pkJoinColumns={
@PrimaryKeyJoinColumn(name="PARTNUMBER",
referencedColumnName="PARTNUMBER"),
@PrimaryKeyJoinColumn(name="REVISION",
referencedColumnName="REVISION")
})
public class Part {
...
}
PERSISTENCE_ORDER_PART_DETAIL разделяет те же первичные ключевые
значения, что и PERSISTENCE_ORDER_PART. Элемент pkJoinColumns в аннотации @SecondaryTable использован, чтобы определять, что первичные ключевые столб-
цы в таблице PERSISTENCE_ORDER_PART_DETAIL являются внешними ключами
в таблицу PERSISTENCE_ORDER_PART. Аннотация @PrimaryKeyJoinColumn уста-
навливает имя столбца первичного ключа и определяет, какой столбец в первичной
таблице имеет на него ссылку. В этом случае имена столбцов первичного ключа та-
блиц PERSISTENCE_ORDER_PART_DETAIL и PERSISTENCE_ORDER_PART —
PARTNUMBER и REVISION соответственно.
Каскадные операции в приложении order
Сущности, которые имеют ссылку на другие сущности, часто имеют зависимость от другой сущности в отношении. Например, пункт — часть заказа, и если заказ
удален, тогда пункт должен быть также удален. Это называется каскадным удалением отношения.
В приложении order есть две зависимости с каскадным удалением сущностей. Если экземпляр сущности Order, на который ссылается LineItem, удален, тогда экзем-
134
пляр сущности LineItem также должен быть удален. Если Vendor, на который ссылает-
ся VendorPart, удален, тогда VendorPart также должен быть удален.
Вы определяете каскадные операции для связи сущностей, устанавливая каскадный элемент на обратной стороне (не обладателе) отношения. Каскадный эле-
мент установлен в ALL в случае Order.lineItems. Это означает, что все операции хра-
нения (удаление, корректировка и так далее) выполняются каскадно от заказов до
пунктов.
Вот отношение, отображающееся в Order:
@OneToMany(cascade=ALL, mappedBy="order") public Collection<LineItem> getLineItems() {
return lineItems;
}
Отношение, отображающееся в LineItem:
@ManyToOne
public Order getOrder() { return order;
}
Типы BLOB и CLOB базы данных приложения order
Таблица PARTDETAIL в базе данных имеет столбец DRAWING типа BLOB. BLOB представляют большие двоичные объекты, которые использованы для хранения двоичных данных, как например, рисунки. Столбец DRAWING отображен на поле
Part.drawing типа java.io.Serializable. Аннотация @Lob использована для поля боль-
шого объекта.
@Column(table="PERSISTENCE_ORDER_PART_DETAIL") @Lob
public Serializable getDrawing() { return drawing;
}
Таблица PERSISTENCE_ORDER_PART_DETAIL также имеет столбец
SPECIFICATION типа CLOB. Данные типа CLOB представляют символьные большие объекты, которые использованы, чтобы загружать длинные строки в столбец VARCHAR. Столбец SPECIFICATION отображен в поле Part.specification типа java.lang.String. Здесь также используется аннотация @Lob, чтобы поле являлось
большим объектом.
@Column(table="PERSISTENCE_ORDER_PART_DETAIL") @Lob
public String getSpecification() { return specification;
}
Обе эти области используют аннотацию @Column и устанавливают табличный
элемент во второстепенную таблицу.
Типы времени в приложении order
Хранимое свойство Order.lastUpdate типа java.util.Date отображено в поле базы данных PERSISTENCE_ORDER_ORDER.LASTUPDATE, которое имеет тип
TIMESTAMP — время в языке SQL.
Чтобы гарантировать соответствующее отображение между этими типами, вы должны использовать аннотацию @Temporal с соответствующим временным типом,
135
определённым в элементе @Temporal. Элементы @Temporal — типа javax.persistence.
TemporalType. Возможные значения:
•DATE — с java.sql.Date;
•TIME — отображается в java.sql.Time;
•TIMESTAMP — отображается в java.sql.Timestamp.
Это секция Order:
@Temporal(TIMESTAMP)
public Date getLastUpdate() { return lastUpdate;
}
12.1.3. Управление сущностями в приложении order
Сеансовый бин RequestBean с хранимым состоянием (stateful) содержит дело-
вую логику и управляет сущностями заказа.
RequestBean использует аннотацию @PersistenceContext, чтобы получить ме-
неджер сущностей, который использован для управления сущностями order в бизнес-
методах RequestBean.
@PersistenceContext private EntityManager em;
EntityManager в этом примере является управляющим менеджером сущностей контейнера, так что контейнер заботится о всех транзакциях, включенных в управление сущностями order.
Создание сущностей
Метод RequestBean.createPart создает новую сущность Part. Метод
EntityManager.persist используется, чтобы сохранить вновь созданную сущность в базе
данных.
Part part = new Part(partNumber, revision,
description,
revisionDate,
specification,
drawing);
em.persist(part);
Единственный сеансовый бин ConfigBean использован, чтобы инициализировать данные в заказе. ConfigBean аннотирован @Startup, которая указывает, что
контейнер EJB создаёт ConfigBean, когда приложение order развертывается. Метод createData аннотирован @PostConstruct и создает начальные сущности, использован-
ные приложением, вызывая деловые методы класса RequestsBean.
Поиск сущностей
Метод RequestBean.getOrderPrice возвращает цену данного заказа, основанно-
го на orderId. Метод EntityManager.find используется для извлечения сущности из базы
данных.
Order order = em.find(Order.class, orderId);
Первый аргумент EntityManager.find является классом сущности, а второй — первичным ключом.
136
Установка связи сущности
Метод RequestBean.createVendorPart создает сущность VendorPart, связанную с
конкретным поставщиком Vendor. Метод EntityManager.persist использован, чтобы со-
хранить вновь созданную сущность VendorPart в базе данных. VendorPart.setVendor и
Vendor.setVendorPart использован, чтобы соединять VendorPart с поставщиком Vendor.
PartKey pkey = new PartKey(); pkey.partNumber = partNumber; pkey.revision = revision;
Part part = em.find(Part.class, pkey);
VendorPart vendorPart = new VendorPart(description, price, part);
em.persist(vendorPart);
Vendor vendor = em.find(Vendor.class, vendorId); vendor.addVendorPart(vendorPart); vendorPart.setVendor(vendor);
Использование запросов
Метод RequestBean.adjustOrderDiscount корректирует скидку, применимую ко
всем заказам. Он использует поименованный запрос findAllOrders, определённый в классе Order :
@NamedQuery(
name="findAllOrders", query="SELECT o FROM Order o"
)
Метод EntityManager.createNamedQuery используется, чтобы запустить запрос. Поскольку запрос возвращает список List всех заказов, используется метод
Query.getResultList.
List orders = em.createNamedQuery( "findAllOrders")
.getResultList();
Метод RequestBean.getTotalPricePerVendor возвращает общую цену всех запчастей для конкретного поставщика. Он использует параметр id в запросе findTotalVendorPartPricePerVendor.
@NamedQuery(
name="findTotalVendorPartPricePerVendor", query="SELECT SUM(vp.price) " +
"FROM VendorPart vp " +
"WHERE vp.vendor.vendorId = :id"
)
При прогоне запроса используется метод Query.setParameter, чтобы установить параметру id значение vendorId.
return (Double) em.createNamedQuery( "findTotalVendorPartPricePerVendor")
.setParameter("id", vendorId)
.getSingleResult();
Для этого запроса использован метод Query.getSingleResult, поскольку запрос
возвращает единственное значение.
137