На следующем шаге будет содержательный пример, так что технические детали не покрываются этим документом. Здесь приводится лишь верхнеуровневое описание технологии и вводятся термины.
Если вспомнить всё то, что мы уже рассмотрели, то можно нарисовать следующую картинку:
Пока
мы рассмотрели только базовые технологии JavaEE, отвечающие за web.
Список рассмотренных технологий не полный. Существует много альтернативных технологий как для разработки view, так и для организации модели и контроллеров.
Изучать их все – занятие достаточно бессмысленное, но с помощью рассмотренных средств можно построить web приложение.
Что до сих пор оставалось за кадром – это набор технологий для организации компонентов бизнес-логики и работы с СУБД, которая чаще всего используется для хранения модели. JavaEE предлагает EJB в качестве технологической основы для разработки этих слоёв приложения.
Спецификация EJB описывает три вида компонентов:
Что касается Message-Driven Beans, то в этом курсе будут приведены только общие сведения, первые же два вида компонентов будут рассматриваться более подробно, т. к. они используются чаще всего.
Entity Beans
Entity Beans – это вид классов системы, которые отображены на базу данных.
Как правило, один класс соответствует одной таблице БД. Пример:
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
@Entity
@Table(name="ASSETS")
public class Asset {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
@Column(name="name", unique=false, updatable=true, nullable=false)
private String name;
@JoinColumn(name="valuable_id", updatable=false, nullable=false)
@ManyToOne(fetch=FetchType.EAGER)
private ValuableObject valuable;
@Column(name="is_archived", updatable=true, nullable=false)
private boolean archived;
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public ValuableObject getValuable() {
return valuable;
}
public void setName(String name) {
this.name = name;
}
public void setValuable(ValuableObject valuable) {
this.valuable = valuable;
}
public void setArchived(boolean archived) {
this.archived = archived;
}
public boolean isArchived() {
return archived;
}
}
Entity bean представляет собой обычный Java-класс (POJO), который снабжается специальными аннотациями-подсказками о том, каким образом класс и его поля соответствуют схеме реляционной СУБД. Контейнер начинает рассматривать класс как entity в том случае, если он аннотирован аннотацией
@Entity.
Вот несколько достаточно типичных способов отображения классов на БД (Object-Relational Mapping):
-
Класс соответствует одной таблице СУБД (@Table)
-
Поле класса соответствует колонке таблицы (@Column)
-
Колонка, являющаяся внешним ключом отображается в поле класса. Типом поля объявляют класс, который соответствует таблице, на которую ссылается внешний ключ. (@JointColumn, @ManyToOne, @OneToMany, …)
-
Первичный ключ соответствует обычному полю с аннотацией @Id. Способ генерации значений для первичного ключа (использовать sequence, триггер или автоинкрементирующееся поле СУБД) – задаётся аннотацией @GeneratedValue
-
Экземпляр класса соответствует записи в таблице
Более подробно о Entity beans можно прочитать тут:
http://java.sun.com/javaee/5/docs/tutorial/doc/bnbpy.html
Кроме Entity-классов существует специальное API, которое позволяет сохранять, извлекать, обновлять и удалять экземпляры entity классов. Этот API называется Java Persistence API. Полная спецификация Java Persistence API (JPA) находится в директории со спецификациями:
ejb-3_0-fr-spec-persistence.pdf
Там также подробно расписано как использовать аннотации для Entity beans.
Существуют несколько реализаций JPA, отличающихся багами и дополнительными фичами. Самые известные из них – Hibernate (JBoss), TopLink (GlassFish), Kodo\OpenJPA (WebLogic).
Session Beans
Session Beans – это компоненты, в которых размещается бизнес-логика. Чаще всего эта логика состоит в том, чтобы получить с помощью JPA или JDBC данные из СУБД, и передать их уровню выше (чаще всего на web).
Собственно, чем Session Bean отличается от обычного класса/объекта:
-
Жизненным циклом экземпляров Session Bean управляет контейнер. Это значит, что вам никогда не придётся использовать new для создания экземпляра Session Bean. Вместо этого используется либо поиск по JNDI, либо механизм, называемый внедрением зависимостей (Dependency Injection).
-
Session Bean может реализовывать т. н. @Local и/или @Remote интерфейсы. Суть их в том, что методы, объявленные в @Local интерфейсе могут быть доступны только приложениям внутри контейнера, а методы @Remote интерфейса могут быть вызваны как локально, так и с помощью механизмов RMI (Remote Method Invocation). На практике, @Remote интерфейсы чаще всего нужны очень редко.
-
При вызове метода Session bean'а, контейнер автоматически стартует транзакцию (если иное поведение не определено явно). Здесь используется механизм JTA транзакций, который будет обсуждаться чуть позже.
-
По окончании вызова метода существуют несколько альтернатив. Если метод выбросил RuntimeException, то транзакция автоматически откатывается. В противном случае (даже если выброшен не RuntimeException, а обычный) происходит автоматический коммит транзакции.
В этом описании были сделаны необходимые упрощения и приведено поведение по-умолчанию, чтобы не загромождать верхнеуровневое описание.
В свою очередь, Session Beans подразделяются на Stateful (с состоянием) и Stateless (без состояния).
О Stateful поговорим тол
ько лишь обзорно, т. к. они практически не используются. Основное внимание сосредоточим на Stateless.
Stateless Beans
Stateless Bean – это компонент без состояния. Считалось, что компоненты без состояния будут лишь вырожденным случаем компонентов с состоянием, однако они используются практически повсеместно.
Какое место в системе может занимать Stateless bean? Ну предположим, что у нас есть торговая информационная система, предоставляющая, скажем web-интерфейс.
Пришедший запрос на покупку попал на обработку в некий контроллер web-интерфейса. В контроллере мы решили обратиться к компоненту PurchaseBean и вызвать у него метод submitOrder(user, product). Этот компонент выполняет все необходимые действия по совершению покупки: пишет в лог, делает записи в БД, возможно ещё что-то. Так вот, PurchaseBean – это отличный кандидат в Stateless Bean.
Прелесть ещё и в том, что доступ к PurchaseBean можно будет предоставить не только для одного web приложения, но и для других систем с помощью SOAP, REST или какого-либо другого механизма. Компонент можно использовать отдельно от view:
Приведём достаточно поверхностный пример Stateless bean'а, чтобы слова не повисали в воздухе:
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
@Stateless
public class InboxManagerBean
implements InboxManagerLocal
{
public List getInboxEntries(User owner) {
Query uq =
em.createNamedQuery(InboxEntry.Queries.
FIND_BY_OWNER);
uq.setParameter(InboxEntry.Properties.OWNER, owner);
return (List
) uq.getResultList();
}
public void removeNote(InboxEntry ie) {
InboxEntry toRemove = em.getReference(InboxEntry.class, ie.getId());
em.remove(toRemove);
}
public void submitNote(InboxEntry ie) {
em.persist(ie);
em.flush();
}
@PersistenceContext
protected EntityManager em;
}
@Local
public interface InboxManagerLocal {
public void submitNote(InboxEntry ie);
public void removeNote(InboxEntry ie);
public List getInboxEntries(User owner);
}
Нами был создан класс, который аннотирован как @Stateless и реализующий @Local интерфейс InboxManagerLocal. Естественно, класс может реализовывать много интерфейсов. Обычно, если нужно дать доступ к методам нескольких интерфейсов, то создают @Local интерфейс как наследника пары-тройки интерфейсов.
Некоторого внимания заслуживает поле EntityManager em. Как видно по коду класса, оно не инициализируется явно. Тут проявляется тот самый механизм Dependency Injection (DI). Контейнер создаёт экземпляр InboxManagerBean сам, проходит по его полям, видит, что поле помечено специальной аннотацией, и инициализирует его.
В качестве альтернативы DI можно использовать явную инициализацию путём поиска нужных ресурсов в JNDI дереве, но это достаточно громоздкий способ.
Заметим также, что инжектируемые ресурсы не относятся к состоянию Stateless bean'а, т. к. могут подставляться контейнером в любой момент времени между вызовами методов. Более того, нет явной гарантии, что em будет всегда одним и тем же объектом на протяжении всей жизни объекта InboxManagerBean.
В данном случае лишь гарантируется доступность экземпляра EntityManager, который позволяет работать с отображенными на базу данных Entity bean'ами.
Следует сказать также, что при вызове методов EntityManager'а могут возникать RuntimeException'ы. Например, что-нибудь типа EntityNotFoundException и т. п. Эти исключения не перехватываются и вызывают откат транзакций, начатых контейнером при вызове методов Stateless bean'а.
Более подробно о Stateless bean'ах можно прочитать в туториале:
http://java.sun.com/javaee/5/docs/tutorial/doc/bnblt.html
Stateful Beans
Разработчики спецификации EJB считали, что технология EJB должна предоставлять возможность хранения состояния между вызовами методов Session Bean'ов. Так появились Stateful Beans, которые отличались от Stateless beans только тем, что они могли иметь состояние.
Суть их в том, что контейнер гарантировал, что один и тот же экземпляр Stateful bean будет использован только одним клиентом. И тем самым он мог содержать состояние взаимодействия сервера и клиента. Типичным примером всегда приводят корзину Интернет-магазина. Она не разделяется между разными пользователями, соответствует одному клиенту и прекращает своё существование после того, как сессия клиента протухает.
Все это, как выяснилось в последствии, не прижилось. Во-первых, работать с такими объектами было достаточно сложно и требовалась определённая дисциплина, во-вторых, попытка сделать управление stateful bean'ами прозрачным для разработчика сделала stateful bean'ы недостаточно контролируемой субстанцией, что пугало набожных разработчиков :) Ну и в-третьих, были более простые и понятные способы сохранять состояние взаимодействия клиента и сервера, такие как обычная HTTP-сессия.
Тем не менее, существует возможность объявлять Stateful bean'ы с помощью аннотации @Stateful
Message-Driven Beans
Message-Driven Beans очень похожи на Stateless Beans, но они отличаются от них тем, что вместо того, чтобы приводиться в действие вызовом метода, они обрабатывают JMS сообщения. Это достаточно специальный раздел, которого мы коснёмся обзорно на следующих шагах.