Примечание:
В этой диаграмме для простоты префикс PERSISTENCE_ORDER_
опущен в именах таблиц.
Java Platform, Enterprise Edition (Java EE) 8 Учебник по Java EE |
Назад | Вперёд | Содержание |
Приложение order
представляет собой простое приложение для инвентаризации и заказа товаров для ведения каталога деталей и размещения подробного заказа этих деталей. В приложении есть сущности, которые представляют детали, поставщиков, заказы и их позиции. Доступ к этим сущностям осуществляется с помощью сессионного компонента с сохранением состояния, который содержит бизнес-логику приложения. Простой сессионный компонент-синглтон инициализирует сущности при развёртывании приложения. Веб-приложение Facelets управляет данными и отображает содержимое каталога.
Информация, содержащаяся в заказе, может быть разделена на элементы. Какой номер заказа? Какие детали включены в заказ? Из каких деталей состоит эта деталь? Кто производит деталь? Каковы технические характеристики детали? Есть ли какие-либо схемы для детали? Приложение order
является упрощённой версией системы заказов, которая имеет ответы на все эти вопросы.
Приложение order
состоит из одного модуля WAR, который включает в себя классы Enterprise-бинов, сущностей, вспомогательные классы, а также XHTML Facelets.
Схема базы данных в Derby для order
показана на рис. 41-1.
Примечание: В этой диаграмме для простоты префикс |
Приложение order
демонстрирует несколько типов отношений сущностей: ссылки на себя, один-к-одному, один-ко-многим, многие-к-одному и однонаправленные.
Здесь рассматриваются следующие темы:
Относящиеся ссылки на себя возникают между полями в одной и той же сущности. Part
имеет поле bomPart
, которое имеет отношение один-ко-многим с полем parts
, которое также находится в Part
. То есть сложная деталь может состоять из множества составляющих её более простых деталей.
Первичный ключ для Part
является составным первичным ключом, комбинацией полей partNumber
и revision
. Этот ключ сопоставляется со столбцами PARTNUMBER
и REVISION
в таблице PERSISTENCE_ORDER_PART
:
...
@ManyToOne
@JoinColumns({
@JoinColumn(name="BOMPARTNUMBER", referencedColumnName="PARTNUMBER"),
@JoinColumn(name="BOMREVISION", referencedColumnName="REVISION")
})
public Part getBomPart() {
return bomPart;
}
...
@OneToMany(mappedBy="bomPart")
public Collection<Part> getParts() {
return parts;
}
...
Part
имеет поле vendorPart
, которое связано отношением один-к-одному с полем part
у VendorPart
. То есть каждая деталь имеет ровно одну соответствующую ей деталь поставщика, и наоборот.
Вот отображение отношения в 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
. Столбец PERSISTENCE_ORDER_VENDOR_PART
таблицы PARTREVISION
ссылается на столбец PERSISTENCE_ORDER_PART
таблицы REVISION
.
CustomerOrder
имеет поле lineItems
, которое связано отношением один-ко-многим с полем LineItem
из customerOrder
. То есть каждый заказ имеет одну или несколько позиций.
LineItem
использует составной первичный ключ, который состоит из полей orderId
и itemId
. Этот составной первичный ключ сопоставляется со столбцами ORDERID
и ITEMID
в таблице PERSISTENCE_ORDER_LINEITEM
. ORDERID
— это внешний ключ для столбца ORDERID
в таблице PERSISTENCE_ORDER_CUSTOMERORDER
. Это означает, что столбец ORDERID
отображается дважды: один раз как поле первичного ключа orderId
и второй раз в качестве поля отношения order
.
Вот отображение отношений в CustomerOrder
:
@OneToMany(cascade=ALL, mappedBy="customerOrder")
public Collection<LineItem> getLineItems() {
return lineItems;
}
Вот отображение отношений в LineItem
:
@Id
@ManyToOne
@JoinColumn(name="ORDERID")
public CustomerOrder getCustomerOrder() {
return customerOrder;
}
Приложение order
использует несколько типов первичных ключей: простые (однозначные) первичные ключи, сгенерированные первичные ключи и составные первичные ключи.
Здесь рассматриваются следующие темы:
VendorPart
использует сгенерированное значение первичного ключа. Таким образом, приложение не устанавливает значение первичного ключа для объектов сущности, а вместо этого полагается на persistence provider для генерации значений первичного ключа. Аннотация @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
используется вместе с элементом @GeneratedValue
ыtrategy=TABLE
. Таким образом, стратегия, используемая для генерации первичных ключей, заключается в использовании таблицы в базе данных. Аннотация @TableGenerator
используется для настройки параметров таблицы генератора. Элемент name устанавливает имя генератора. Для VendorPart
это vendorPartGen
.
Таблица PERSISTENCE_ORDER_SEQUENCE_GENERATOR
, двумя столбцами которой являются GEN_KEY
и GEN_VALUE
, будет хранить сгенерированные значения первичного ключа. Эту таблицу можно использовать для генерации первичных ключей других сущностей, поэтому элемент pkColumnValue
установлен в VENDOR_PART_ID
, чтобы отличать сгенерированные первичные ключи этой сущности от сгенерированных первичных ключей других сущностей. Элемент allocSize
указывает величину, которую нужно увеличить при выделении значений первичного ключа. В этом случае каждый первичный ключ VendorPart
будет увеличиваться на 10.
Поле первичного ключа vendorPartNumber
имеет тип Long
, так как поле сгенерированного первичного ключа должно быть целочисленным.
Составной первичный ключ состоит из нескольких полей и соответствует требованиям, описанным в Первичные ключи в сущностях. Чтобы использовать составной первичный ключ, вы должны создать класс-обёртку (wrapper).
В order
две сущности используют составные первичные ключи: Part
и LineItem
.
Part
использует класс-обёртку (wrapper) PartKey
. Первичный ключ Part
представляет собой комбинацию номера детали и номера редакции. PartKey
инкапсулирует этот первичный ключ.
LineItem
использует класс LineItemKey
. Первичный ключLineItem
представляет собой комбинацию номера заказа и номера позиции. LineItemKey
инкапсулирует этот первичный ключ.
Это класс-обёртка (wrapper) LineItemKey
составного первичного ключа:
package javaeetutorial.order.entity;
import java.io.Serializable;
public final class LineItemKey implements Serializable {
private Integer customerOrder;
private int itemId;
public LineItemKey() {}
public LineItemKey(Integer order, int itemId) {
this.setCustomerOrder(order);
this.setItemId(itemId);
}
@Override
public int hashCode() {
return ((this.getCustomerOrder() == null
? 0 : this.getCustomerOrder().hashCode())
^ ((int) this.getItemId()));
}
@Override
public boolean equals(Object otherOb) {
if (this == otherOb) {
return true;
}
if (!(otherOb instanceof LineItemKey)) {
return false;
}
LineItemKey other = (LineItemKey) otherOb;
return ((this.getCustomerOrder() == null
? other.getCustomerOrder == null : this.getOrderId()
.equals(other.getCustomerOrder()))
&& (this.getItemId == oother.getItemId()));
}
@Override
public String toString() {
return "" + getCustomerOrder() + "-" + getItemId();
}
public Integer getCustomerOrder() {
return customerOrder;
}
public void setCustomerOrder(Integer order) {
this.customerOrder = order;
}
public int getItemId() {
return itemId;
}
public void setItemId(int itemId) {
this.itemId = itemId;
}
}
Аннотация @IdClass
используется для указания класса первичного ключа в классе сущности. В LineItem
@IdClass
используется следующим образом:
@IdClass(LineItemKey.class)
@Entity
...
public class LineItem implements Serializable {
...
}
Два поля в LineItem
помечены аннотацией @Id
, чтобы пометить эти поля как часть составного первичного ключа:
@Id
public int getItemId() {
return itemId;
}
...
@Id
@ManyToOne
@JoinColumn(name="ORDERID")
public CustomerOrder getCustomerOrder() {
return customerOrder;
}
Для customerOrder
вы также используете аннотацию @JoinColumn
для указания имени столбца в таблице и того, что этот столбец является перекрывающимся внешним ключом, указывающим на PERSISTENCE_ORDER_CUSTOMERORDER
столбец ORDERID
таблицы (см. отношение «один ко многим», сопоставленное перекрывающимся первичным и внешним ключам). То есть customerOrder
будет установлен сущностью CustomerOrder
.
В конструкторе LineItem
номер позиции (LineItem.itemId
) устанавливается с помощью метода CustomerOrder.getNextId
:
public LineItem(CustomerOrder order, int quantity, VendorPart vendorPart) {
this.customerOrder = order;
this.itemId = order.getNextId();
this.quantity = quantity;
this.vendorPart = vendorPart;
}
CustomerOrder.getNextId
подсчитывает количество текущих позиций, добавляет 1 и возвращает это число:
@Transient
public int getNextId() {
return this.lineItems.size() + 1;
}
Part
требует аннотации @Column
для двух полей, которые образуют составной первичный ключ Part
, поскольку составной первичный ключ Part
является перекрывающимся первичным и внешним ключом:
@IdClass(PartKey.class)
@Entity
...
public class Part implements Serializable {
...
@Id
@Column(nullable=false)
public String getPartNumber() {
return partNumber;
}
...
@Id
@Column(nullable=false)
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 implements Serializable {
...
}
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
в отношениях сущностей есть две зависимости каскадного удаления. Если CustomerOrder
, с которым связан LineItem
, удалён, LineItem
также следует удалить. Если Vendor
, с которым связан VendorPart
, удалён, VendorPart
также следует удалить.
Вы указываете каскадные операции для отношений сущностей, устанавливая элемент cascade
на обратной стороне (не стороне-владельце) отношения. Каскадный элемент имеет значение ALL
в случае CustomerOrder.lineItems
. Это означает, что все персистентные операции (удаление, обновление и т. д.) каскадируются от заказов к позициям.
Вот отображение отношений в CustomerOrder
:
@OneToMany(cascade=ALL, mappedBy="customerOrder")
public Collection<LineItem> getLineItems() {
return lineItems;
}
Вот отображение отношений в LineItem
:
@Id
@ManyToOne
@JoinColumn(name="ORDERID")
public CustomerOrder getCustomerOrder() {
return customerOrder;
}
Таблица 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
и задают элемент table
для вторичной таблицы.
Персистентное свойство CustomerOrder.lastUpdate
имеет тип java.util.Date
, отображается в поле базы данных PERSISTENCE_ORDER_CUSTOMERORDER.LASTUPDATE
, которое имеет тип SQL TIMESTAMP
. Чтобы обеспечить правильное сопоставление между этими типами, необходимо использовать аннотацию @Temporal
с правильным временны́м типом, указанным в элементе @Temporal
. Элементы@Temporal
имеют тип javax.persistence.TemporalType
. Возможные значения
DATE
, который отображается на java.sql.Date
TIME
, который отображается на java.sql.Time
TIMESTAMP
, который отображается на java.sql.Timestamp
Вот соответствующий раздел CustomerOrder
:
@Temporal(TIMESTAMP)
public Date getLastUpdate() {
return lastUpdate;
}
Сессионный компонент с состоянием RequestBean
содержит бизнес-логику и управляет объектами order
. RequestBean
использует аннотацию @PersistenceContext
для извлечения объекта entity manager-а, который используется для управления сущностями 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
используется для инициализации данных в order
. ConfigBean
помечается @Startup
, что указывает на то, что контейнер EJB должен создать ConfigBean
при развёртывании order
. Метод createData
аннотируется @PostConstruct
и создаёт начальные сущности, используемые order
, вызывая бизнес-методы RequestBean
.
Бизнес-метод RequestBean.getOrderPrice
возвращает цену заказа по его orderId
. Метод EntityManager.find
используется для извлечения сущности из базы данных:
CustomerOrder order = em.find(CustomerOrder.class, orderId);
Первый аргумент EntityManager.find
является классом сущности, а второй — первичным ключом.
Бизнес-метод RequestBean.createVendorPart
создаёт VendorPart
, связанный с конкретным объектом Vendor
. Метод EntityManager.persist
используется для сохранения вновь созданного объекта VendorPart
в базе данных, а методы VendorPart.setVendor
и Vendor.Vendor.setVendorPart
используются для связи VendorPart
с Vendor
:
PartKey pkey = new PartKey();
pkey.setPartNumber(partNumber);
pkey.setRevision(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
, определённый в CustomerOrder
:
@NamedQuery(
name="findAllOrders",
query="SELECT co FROM CustomerOrder co " +
"ORDER BY co.orderId"
)
Для выполнения запроса используется метод EntityManager.createNamedQuery
. Поскольку запрос возвращает List
всех заказов, используется метод Query.getResultList
:
List orders = em.createNamedQuery(
"findAllOrders")
.getResultList();
Бизнес-метод RequestBean.getTotalPricePerVendor
возвращает общую стоимость всех деталей для конкретного поставщика. Этот метод использует именованный параметр id
, определённый в именованном запросе findTotalVendorPartPricePerVendor
, определённом в VendorPart
:
@NamedQuery(
name="findTotalVendorPartPricePerVendor",
query="SELECT SUM(vp.price) " +
"FROM VendorPart vp " +
"WHERE vp.vendor.vendorId = :id"
)
При выполнении запроса метод Query.setParameter
используется для установки именованному параметру id
значения vendorId
для параметра RequestBean.getTotalPricePerVendor
:
return (Double) em.createNamedQuery(
"findTotalVendorPartPricePerVendor")
.setParameter("id", vendorId)
.getSingleResult();
Метод Query.getSingleResult
используется для этого запроса, поскольку запрос возвращает одно значение.
Вы можете использовать IDE NetBeans или Maven для сборки, упаковки, развёртывания и запуска приложения order
. Сначала вы создадите таблицы базы данных в Apache Derby.
Здесь рассматриваются следующие темы:
Удостоверьтесь, чтобы GlassFish Server был запущен (см. Запуск и остановка сервера GlassFish).
Если сервер базы данных ещё не запущен, запустите его, следуя инструкциям в Запуск и остановка Apache Derby.
В меню «Файл» выберите «Открыть проект».
В диалоговом окне «Открыть проект» перейдите к:
tut-install/examples/persistence
Выберите каталог order
.
Нажмите Открыть проект.
На вкладке «Проекты» кликните правой кнопкой мыши проект order
и выберите «Выполнить».
Среда IDE NetBeans открывает веб-браузер по следующему URL:
http://localhost:8080/order/
Удостоверьтесь, чтобы GlassFish Server был запущен (см. Запуск и остановка сервера GlassFish).
Если сервер базы данных ещё не запущен, запустите его, следуя инструкциям в Запуск и остановка Apache Derby.
В окне терминала перейдите в:
tut-install/examples/persistence/order/
Введите следующую команду:
mvn install
Это компилирует исходные файлы и упаковывает приложение в файл WAR, расположенный в tut-install/examples/persistence/order/target/order.war
. Затем WAR-файл будет развёрнут в GlassFish Server.
Чтобы создать и обновить данные заказа, откройте веб-браузер по следующему URL:
http://localhost:8080/order/
Назад | Вперёд | Содержание |