Взаимосвязь компонентов


Последний раздел, который мы рассмотрим здесь, это, возможно, наиболее значительное улучшение, которое появилось в EJB 2.0, наряду с локальными интерфейсами. Эта способность, которая позволяет разработчикам компонентов устанавливать взаимосвязи между сущностными компонентами путем описания их в дескрипторе развертывания. Позвольте мне объяснить.

До сих пор мы реализовывали сущностные компоненты, такие как Movie и AutoCounter, которые были полностью самодостаточными. То есть, они содержали внутри всю информацию, которая им необходима, чтобы быть целостным блоком информации. Например, сущностный компонент Movie содержал всю информацию о фильме. Однако, к сожалению, в реальном мире приложения состоят не только из таких простых объектов. Даже наше приложение JavaTheater, как только мы начнем добавлять такие компоненты, как показ, станет гораздо сложнее.

Показ, фактически, формируется из фильма, времени показа и кинотеатра. Опираясь на это описание, безопасно сказать, что показ может иметь только один фильм, но один и тот же фильм может быть в разных показах - классическое отношение один ко многим. С точки зрения объектного подхода, реализация такого отношения тривиальна: объект показа содержит указатель на объект фильма, а объект фильма содержит коллекцию ссылок на объекты показа.

Проблема, на мой взгляд, состоит в том, что наши объекты являются постоянными сущностями, так что ссылки, представленные взаимосвязями, должны быть трансформированы во что-то такое, что может храниться в базе данных, а позднее быть получено и трансформировано обратно в ссылку на сущность, которую она изначально представляла. Этот процесс гораздо сложнее, чем это кажется, и EJB 1.1 предоставлял небольшую помощь разработчику; но EJB 2.0 покрыла эту недостаточность, и ввела концепцию взаимосвязей, управляемых контейнером. Благодаря этой возможности, разработчик может указать в дескрипторе развертывания, как связаны два сущностных компонента (в нашем случае это Movie и Show), какие поля участвуют в связях, какие множатся. Здесь указывается, сколько сущностей существует на каждой стороне такой связи.

Как всегда, мы будем использовать практический пример для прояснения концепции, но прежде чем вы построите и развернете код, я рекомендую, чтобы вы полностью очистили базу данных, которую вы использовали в предыдущих примерах. Самый простой способ сделать это, просто удалите все файлы, которые хранят ваши данные (и не беспокойтесь, они будут восстановлены заново, когда вы перезапустите JBoss). Выгрузите JBoss а затем, в каталоге установки JBoss, найдите каталог server/default/db/hypersonic (пожалуйста, обратите внимание, что если вы используете версию JBoss отличную от 3.04, структура директорий может значительно отличаться). Этот директорий содержит все файлы данных HypersonicSQL, имена которых имеют различные расширения. Просто удалите их все (или перенесите их в другой директорий, если хотите их сохранить) и перезапустите JBoss. Проверьте, что файлы снова восстановлены, а затем продолжите упражнение.

Далее мы займемся тем, что начнем реализовывать новый сущностный CMP компонент, который представляет показ. Показ получает доступ к одному фильму, а фильм получает доступ к нескольким показам. Ниже вы можете видеть реализацию нового сущностного компонента для нашего приложения, компонента Show, который вы найдете в директории example11. В нашем дизайне Show состоит из Фильма, времени показа (строка) и количества посадочных мест (целое). Я умышленно оставил театр за пределами рассмотрения для простоты.

//:example11/src/ejb-tier/javatheater/ejb/implementation/ShowBean.java
package javatheater.ejb.implementation;

import javax.ejb.*;

import javax.naming.*;

import javatheater.ejb.*;

/**
* Это код реализации сущностного компонента
<code>Show</code> Показ имеет
* идентификатор id (который также является первичным ключом), фильм, время
* показа и число посадочных мест.
*
*
@see javatheater.ejb.ShowHome
*
@see javatheater.ejb.Show
* @ejb:bean name="Show" local-jndi-name="javatheater/Show" view-type="local"
*           type="CMP" primkey-field="id" reentrant="False"
* @ejb:pk class="java.lang.Integer"
* @ejb:home local-class="javatheater.ejb.ShowHome"
* @ejb:interface local-class="javatheater.ejb.Show"
* @ejb:finder signature="Collection findByMovie(java.lang.Integer moviePK)"
*             query="SELECT OBJECT(s) FROM Show s WHERE s.movie.id=?1"
* @ejb:finder signature="java.util.Collection findAll()" query="SELECT
*             OBJECT(s) FROM Show s"
* @ejb:env-entry name="CounterName" value="ShowPKCounter"
*                type="java.lang.String"
*/
public abstract class ShowBean implements EntityBean {
  
/**
    * Проверка первичного ключа и инициализация параметров компонента.
    *
    * @ejb:create-method
    */
  
public Integer ejbCreate(Integer id, Movie movie, String showTime,
        
int availableSeats) throws CreateException {
     
if (id == null)
        
throw new CreateException("Id can not be null");
     
if (id.intValue() == 0)
        
throw new CreateException("Id can not be zero");
     
if (movie == null)
        
throw new CreateException("A movie must be provided");
      setId
(id);
      setShowTime
(showTime);
      setAvailableSeats
(availableSeats);
     
// Обратите внимание, что поля взаимосвязей не могут быть
      // модифицированны
      // или установлены в ejbCreate(). Так что мы инициализируем
      // поля фильма в методе ejbPostCreate() (ниже).
     
return null;
  
}
  
  
public void ejbPostCreate(Integer id, Movie movie, String showTime,
        
int availableSeats) {
     
setMovie(movie);
  
}
  
  
/**
    * Домашний метод возвращает имя счетчика (строка) для генерации уникального
    * первичного ключа для Show. Реальное имя определяется, как переменная
    * окружения для компонента Show.
    *
    * @ejb:home-method
    */
  
public String ejbHomeGetCounterName() {
     
try {
        
Context initial = new InitialContext();
        
return (String) initial.lookup("java:comp/env/CounterName");
     
}
     
catch (NamingException e) {
        
throw new EJBException(e);
     
}
   }
  
  
/**
    * Возвращается Show id, который также является первичным ключом для показа.
    *
    * @ejb:pk-field
    * @ejb:persistent-field
    * @ejb:interface-method
    */
  
abstract public Integer getId();
  
  
/**
    * @ejb:persistent-field
    */
  
abstract public void setId(Integer id);
  
  
/**
    * Устанавливает фильм из этого показа. Обратите внимание, что это не CMP
    * поле, а поле Взаимосвязей, Управляемое Контенером (Container Managed
    * Releationship (CMR)) (смотрите сгенерированный дескриптор развертывания).
    *
    * @ejb:interface-method
    * @ejb:relation name="Show-Movie" role-name="Show-has-a-Movie"
    */
  
abstract public void setMovie(Movie movie);
  
  
/**
    * Получает фильм из этого показа. Обратите вниманиен, что параметр фильма -
    * это поле CMR.
    *
    *
@see #setMovie(Movie)
    * @ejb:interface-method
    */
  
abstract public Movie getMovie();
  
  
/**
    * Возвращает время показа.
    *
    * @ejb:persistent-field
    * @ejb:interface-method
    */
  
abstract public String getShowTime();
  
  
/**
    * Устанавливает время показа.
    *
    * @ejb:persistent-field
    * @ejb:interface-method
    */
  
abstract public void setShowTime(String showTime);
  
  
/**
    * Возвращает доступное количество мест.
    *
    * @ejb:persistent-field
    * @ejb:interface-method
    */
  
abstract public int getAvailableSeats();
  
  
/**
    * Устанавливает доступное количество мест.
    *
    * @ejb:persistent-field
    * @ejb:interface-method
    */
  
abstract public void setAvailableSeats(int availableSeats);
} // /:~

В первой части кода нет ничего нового или странного. Это простой сущностный компонент CMP с локальным представлением и парой методов поиска. Метод ejbCreate(…) принимает всю информацию, которая необходима для создания нового просмотра: первичный ключ, ссылку на Фильм, время показа и количество посадочных мест. Вы, наверное, ожидали, что вся эта информация будет сохранена в свойствах, но здесь есть кое-что новое: ссылка на Movie не устанавливается в ejbCreate(…), а вместо этого устанавливается в ejbPostCreate(…). Это происходит потому, что поле movie компонента Show представляет собой постоянные связи, так что оно зависит от правильных первичных ключей обоих задействованных партнеров: Show и Movie; а как вы знаете, вы не можете полагаться на значение первичного ключа компонента в процессе нахождения в методе ejbCreate(…). Оставшаяся часть кода просто определяет поля CMP компонента Show, за исключением поля movie, которое является чем-то новым для нас.

Поле movie в компоненте Show не является CMP полем, а является полем взаимосвязи, управляемое Контейнером (Container Managed Relationship (CMR)), оно обрабатывается контейнером не так, как другие поля. Оно требует отличного описания в дескрипторе развертывания, от CMP полей, так что EJBDoclet работает с тэгами, специально предназначенными для описания взаимосвязей (ejb:relation).

Прежде чем объяснить, что означает этот тэг и информация в дескрипторе развертывания, нам необходимо понять, как моделируется взаимосвязь сущностей в EJB. Прежде всего, взаимосвязь сущностей сделана, конечно, для двух сущностных компонентов; второе, только определенные поля каждого компонента участвуют в связи; третье, каждая сторона имеет величину множества (например, фильм имеет много показов, но показ имеет только один фильм). Вся эта информация должна находится в дескрипторе развертки, но это не все: так как обычно существует несколько взаимосвязей, управляемых контейнером, они должны быть поименованы для контейнера и (особенно) для развертывателя компонента (Bean Deployer).

Когда вы используете EJBDoclet, все что вам нужно сделать - это указать, что поле представляет взаимосвязь, управляемую контейнером, и вы должны сделать это для обеих составляющих пары, вовлеченных во взаимосвязь. В зависимости от типа полей, описывающих взаимосвязь (единственная ссылка на сущность или коллекция) EJBDoclet сгенерирует информацию в дескрипторе развертывания с правильной величиной множества. Однако, вы должны указать имя взаимосвязи и имя роли. Вот что в точности делает вырезанный код из приведенного выше примера (я только удалил прозу из комментариев):

/**
    * @ejb:interface-method
    * @ejb:relation name="Show-Movie" role-name="Show-has-a-Movie"
    */

  
abstract public void setMovie(Movie movie);

В этом коде мы сообщаем, что хотим создать поле movie в сущностном компоненте Show (определяется по имени метода-сеттера), которое будет являться взаимосвязью, управляемой контейнером. Мы также сообщаем, что эта взаимосвязь называется "Show-Movie" и что имя роли в этой взаимосвязи будет "Show-has-a-Movie". Эти имена могут быть использованы, например, для ассоциации уровня доступа по безопасности с ролью. Мощность множества определяется типом поля, который EJBDoclet может получить из сигнатуры сеттера. В данном случае тип является "ссылкой на Movie", так что EJBDoclet знает, что на этой стороне взаимосвязи мощность множества равна "единице".

Взаимосвязь существует только между двумя партнерами, и другой участник - это, конечно, компонент Movie. Ниже вы можете увидеть новый код, который будет добавлен в сущностный компонент Movie из example11:

/**
* @ejb:interface-method
* @ejb:relation name="Show-Movie" role-name="Movie-has-many-Shows"
*/

 
public abstract Collection getShows();

 
/**
   * @ejb:interface-method
   */

 
public abstract void setShows(Collection shows);

Здесь мы определяем новое поле для компонента Movie с названием show. Это поле является коллекцией, так как один фильм может участвовать во многих показах. Поле участвует в той же самой взаимосвязи ("Show-Movie"), которую мы описали для компонента Show, но здесь определяем другую роль ("Movie-has-many-Shows").

Основываясь на имени взаимосвязи, которое вы указали для обоих компонентов, и на типах полей, вовлеченных во взаимосвязь (единственный объект или коллекция), EJBDoclet генерирует следующую информацию в дескрипторе развертывания:

<relationships>
    <ejb-relation>
        <ejb-relation-name>
            Show-Movie
        </ejb-relation-name>
        <ejb-relationship-role>
            <ejb-relationship-role-name>
                Movie-has-many-Shows
            </ejb-relationship-role-name>
            <multiplicity>One</multiplicity>
            <relationship-role-source>
                <ejb-name>Movie</ejb-name>
            </relationship-role-source>
            <cmr-field>
                <cmr-field-name>
                    shows
                </cmr-field-name>
                <cmr-field-type>
                    java.util.Collection
                </cmr-field-type>
            </cmr-field>
        </ejb-relationship-role>
        <ejb-relationship-role>
            <ejb-relationship-role-name>
                Show-has-a-Movie
            </ejb-relationship-role-name>
            <multiplicity>Many</multiplicity>
            <relationship-role-source>
                <ejb-name>Show</ejb-name>
            </relationship-role-source>
            <cmr-field>
                <cmr-field-name>
                    movie
                </cmr-field-name>
            </cmr-field>
        </ejb-relationship-role>
    </ejb-relation>
</relationships>

Это новый раздел дескриптора развертывания, который не входит в круг знакомых элементов <enterprise-beans>, виденных вами до этого, и он служит для перечисления всех взаимосвязей, управляемых контейнером, в вашем приложении. В нашем случае есть только одна взаимосвязь, имя которой "Show-Movie". Две роли во взаимосвязи описываются своими собственными соответствующими элементами, наряду с мощностью множества, именем и типом вовлеченного поля.

Используя эту информацию, контейнер будет заботиться о постоянстве взаимосвязей в вашей базе данных, и он также будет генерировать реализацию сеттеров и геттеров для CMR поля вашего компонента. Как реально взаимосвязь отражается на базу данных, зависит от комбинации нескольких факторов: реализации контейнера, базы данных, которую вы используете, интеграции между этими двумя элементами и отображения, которое вы предоставили (также специфичное для контейнера). В нашем случае, при использовании JBoss с его базой данных по умолчанию (HypersonicSQL - простейшая база данных) и отображением по умолчанию, контейнер генерирует дополнительное поле в таблице, ассоциированное с сущностным компонентом Show, и в этом поле хранит первичный ключ фильма, ассоциированный с этим показом. В других таблицах нет дополнительных полей, и в этом случае нет ассоциированных таблиц, так как взаимосвязь между фильмом и показом достаточно проста, чтобы быть смоделированной таким образом. Когда вы вызываете метод getMovie( ) для экземпляра используемого вами Show, сгенерированная реализация метода выполняет SQL запрос для таблицы Show, который ищет записи с тем же самым первичным ключом, который имеет используемый вами экземпляр Show, и возвращает первичный ключ Movie; первичный ключ Movie используется затем, чтобы создать в памяти экземпляр Movie, который инициализируется информацией о фильме, и в конце концов эта ссылка возвращается клиенту. Если вы вызовите метод getShows( ) для экземпляра Movie, выполняется другой запрос, который получает все первичные ключи Show, которые содержат тот же первичный ключ Movie, что и экземпляр, для которого вы вызвали метод.

Все эти сложности, конечно же, спрятаны от вас контейнером, плюс к этому, при использовании EJBDoclet'а вам даже ненужно беспокоится о содержимом дескриптора развертывания. Все, что вам нужно сделать - это указать абстрактный метод для обеих сторон взаимосвязи (обоих вовлеченных компонентов) и использовать соответствующие тэги EJBDoclet'а. Теперь мы можем использовать новый метод, имеющийся в компонентах Movie и Show, и проверить, что взаимосвязь работает, как это и ожидается. Так как оба компонента не имеют удаленного представления, и не могут использоваться клиентом напрямую, я добавил несколько методов в наш façade компонент. Я также ввел новый объект-значение, ShowItem, который мы используем для передачи структурированной информации о показе между уровнями. Вот код нового класса ShowItem:

//:example11/src/ejb-tier/javatheater/valueobject/ShowItem.java
package javatheater.valueobject;

import java.io.Serializable;

/**
* ShowItem является объектом-значением, используемом для передачи информации
* между уровнем клиента и уровнем ejb.
*/
public class ShowItem implements Serializable {
  
public Integer pk;
  
public MovieItem movie;
  
public String showTime;
  
public int availableSeats;
  
  
public ShowItem(Integer pk, MovieItem movie, String showTime,
        
int availableSeats) {
     
this.pk = pk;
     
this.movie = movie;
     
this.showTime = showTime;
     
this.availableSeats = availableSeats;
  
}
  
  
public String toString() {
     
return "[" + pk.toString() + "] " + movie.title + ", " + showTime
            +
", " + availableSeats;
  
}
}
// /:~

Концептуально этот класс не сильно отличается от класса MovieItem. Отличие заметно только в одном публичном поле, являющемся ссылкой на объект MovieItem, вместо присутствия простого типа данных или класса. То, что вы здесь видите, происходит при наличии взаимосвязей между компонентами, объекты-значения также должны отражать эту связь, и могут быть структурированы, как web объекты, а не как набор простых типов данных. Иногда это называется "простейшей агрегацией".

Ниже приведена реализация сессионного компонента без состояний ShowManager, который может использоваться для изменения содержимого каталога. Он отличается от предыдущего примера тем, что поддерживает новый сущностный компонент Show.

//:example11/src/ejb-tier/javatheater/ejb/implementation/ShowManagerBean.java
package javatheater.ejb.implementation;

import javatheater.ejb.*;

import javax.ejb.*;

import javax.naming.*;

import java.rmi.RemoteException;

/**
* @ejb:bean type = "Stateless" name = "ShowManager" jndi-name =
*           "javatheater/ShowManager" reentrant = "false"
* @ejb:home remote-class="javatheater.ejb.ShowManagerHome"
* @ejb:interface remote-class="javatheater.ejb.ShowManager"
*/
public abstract class ShowManagerBean implements SessionBean {
  
AutoCounterHome autoCounterHome = null;
   MovieHome movieHome =
null;
   ShowHome showHome =
null;
   String movieCounterName =
null;
   String showCounterName =
null;
  
  
/**
    * @ejb:create-method
    */
  
public void ejbCreate() throws CreateException {
     
try {
        
Context initial = new InitialContext();
        
// Получает домашний интерйес для EJB, который мы будем использовать
         // позже
        
autoCounterHome = (AutoCounterHome) initial
               .lookup
("javatheater/AutoCounter");
         movieHome =
(MovieHome) initial.lookup("javatheater/Movie");
         showHome =
(ShowHome) initial.lookup("javatheater/Show");
         movieCounterName = movieHome.getCounterName
();
         showCounterName = showHome.getCounterName
();
     
}
     
catch (NamingException e) {
        
throw new EJBException(e);
     
}
   }
  
  
/**
    * Создаем новый фильм и возвращаем значение первичного ключа для фильма.
    *
    * @ejb:interface-method
    */
  
public Integer createMovie(String movieName) throws TheaterException {
     
try {
        
Integer pk = new Integer(getCounter(movieCounterName).getNext());
         movieHome.create
(pk, movieName);
        
return pk;
     
}
     
catch (CreateException e) {
        
throw new TheaterException(e);
     
}
     
catch (RemoteException e) {
        
throw new EJBException(e);
     
}
   }
  
  
/**
    * @ejb:interface-method
    */
  
public Integer createShow(Integer moviePK, String showtime, int seats)
        
throws TheaterException {
     
try {
        
Movie movie = movieHome.findByPrimaryKey(moviePK);
         Integer showPK =
new Integer(getCounter(showCounterName).getNext());
         showHome.create
(showPK, movie, showtime, seats);
        
return showPK;
     
}
     
catch (FinderException e) {
        
throw new TheaterException(e);
     
}
     
catch (CreateException e) {
        
throw new TheaterException(e);
     
}
     
catch (RemoteException e) {
        
throw new EJBException(e);
     
}
   }
  
  
/**
    * Удаляет фильм по заданному значению первичного ключа.
    *
    * @ejb:interface-method
    */
  
public void deleteMovie(Integer moviePk) throws TheaterException {
     
try {
        
Movie movie = movieHome.findByPrimaryKey(moviePk);
        
if (movie.getShows().size() != 0)
           
throw new TheaterException(
                 
"Can't delete movie: it appears in one or more shows");
         movie.remove
();
     
}
     
catch (FinderException e) {
        
throw new TheaterException(e);
     
}
     
catch (RemoveException e) {
        
throw new TheaterException(e);
     
}
   }
  
  
/**
    * Удаляет показ по заданному первичному ключу.
    *
    * @ejb:interface-method
    */
  
public void deleteShow(Integer showPK) throws TheaterException {
     
try {
        
Show show = showHome.findByPrimaryKey(showPK);
         show.remove
();
     
}
     
catch (FinderException e) {
        
throw new TheaterException(e);
     
}
     
catch (RemoveException e) {
        
throw new TheaterException(e);
     
}
   }
  
  
/**
    * Вспомогательный метод для получения генератора первичного ключа для
    * компонента Movie.
    */
  
AutoCounter getCounter(String counterName) throws RemoteException,
         CreateException
{
     
AutoCounter counter = null;
     
try {
        
counter = autoCounterHome.findByPrimaryKey(counterName);
     
}
     
catch (FinderException e) {
      }
     
if (counter == null)
        
counter = autoCounterHome.create(counterName);
     
return counter;
  
}
}
// /:~

Эта новая реализация содержит два метода для создания и удаления показов в системе. Реализация этих методов достаточно проста и не содержит ничего удивительного для вас. Более интересным является изменение реализации существующего до этого метода deleteMovie(…), поскольку это изменение явно выделено. Как вы можете видеть из кода, в этот метод была добавлена проверка, что фильм, который вы хотите удалить, не участвует в показах. Это имеет смысл: если оставить показ, но удалить фильм, это не сделает аудиторию счастливой. Это классический пример нарушения ссылочной целостности системы. Другими словами, попытка удаления сущности фильма, являющегося частью взаимосвязи с показом, может привести к несостоятельности данных в системе.

Вы видите в методе deleteMovie(…) одно из возможных решений этой проблемы: реализация правил ссылочной целостности вашей системы в façade-компоненте (компоненте-фасаде). Это не единственный возможный подход, особенно, если ссылочная целостность реализуется в базе данных, как об этом знают многие DB администраторы. Это особый случай, когда вы помещаете ваш новый сущностный компонент поверх существующей базы данных, которая также может использовать хранимые процедуры для реализации определенных бизнес-функций. В таком случае, ваши сущностные и сессионные компоненты могут получить развитие логики в базе данных, при этом не потребуется дополнительного Java кода в компоненте для слежения за ссылочной целостностью, за исключением того кода, который перехватывает исключения, сгенерированные базой данных при срабатывании ограничительных правил.

Последними изменениями в EJB из example11 заключаются в добавлении в сессионный компонент ShowListing метода получения всех существующих показов. Вот его код:

//:example11/src/ejb-tier/javatheater/ejb/implementation/ShowListingBean.java
package javatheater.ejb.implementation;

import javatheater.ejb.*;

import javatheater.valueobject.*;

import javax.ejb.*;

import javax.naming.*;

import java.util.Collection;

import java.util.Iterator;

/**
* @ejb:bean type = "Stateless" name = "ShowListing" jndi-name =
*           "javatheater/ShowListing" reentrant = "false"
* @ejb:home remote-class="javatheater.ejb.ShowListingHome"
* @ejb:interface remote-class="javatheater.ejb.ShowListing"
*/
public abstract class ShowListingBean implements SessionBean {
  
MovieHome movieHome = null;
   ShowHome showHome =
null;
  
  
/**
    * @ejb:create-method
    */
  
public void ejbCreate() throws CreateException {
     
try {
        
Context initialContext = new InitialContext();
         movieHome =
(MovieHome) initialContext.lookup("javatheater/Movie");
         showHome =
(ShowHome) initialContext.lookup("javatheater/Show");
     
}
     
catch (NamingException e) {
        
throw new EJBException(e);
     
}
   }
  
  
/**
    * @ejb:interface-method
    */
  
public MovieItem getMovie(Integer moviePK) {
     
try {
        
Movie movie = movieHome.findByPrimaryKey(moviePK);
        
return new MovieItem(movie.getId(), movie.getTitle());
     
}
     
catch (FinderException e) {
        
return null;
     
}
   }
  
  
/**
    * @ejb:interface-method
    */
  
public MovieItem[] getMovie(String title) {
     
try {
        
Collection foundMovies = movieHome.findByTitle(title);
         MovieItem
[] items = new MovieItem[foundMovies.size()];
        
int index = 0;
         Iterator movies = foundMovies.iterator
();
        
while (movies.hasNext()) {
           
Movie movie = (Movie) movies.next();
            items
[index++] = new MovieItem(movie.getId(), movie.getTitle());
        
}
        
return items;
     
}
     
catch (FinderException e) {
        
throw new EJBException(e);
     
}
   }
  
  
/**
    * @ejb:interface-method
    */
  
public MovieItem[] getMovies() {
     
try {
        
Collection foundMovies = movieHome.findAll();
         MovieItem
[] items = new MovieItem[foundMovies.size()];
        
int index = 0;
         Iterator movies = foundMovies.iterator
();
        
while (movies.hasNext()) {
           
Movie movie = (Movie) movies.next();
            items
[index++] = new MovieItem(movie.getId(), movie.getTitle());
        
}
        
return items;
     
}
     
catch (FinderException e) {
        
throw new EJBException(e);
     
}
   }
  
  
/**
    * @ejb:interface-method
    */
  
public ShowItem[] getShows() {
     
try {
        
Collection foundShows = showHome.findAll();
         ShowItem
[] items = new ShowItem[foundShows.size()];
        
int index = 0;
         Iterator shows = foundShows.iterator
();
        
while (shows.hasNext()) {
           
Show show = (Show) shows.next();
            Movie movie = show.getMovie
();
            items
[index++] = new ShowItem(show.getId(), new MovieItem(movie
                  .getId
(), movie.getTitle()), show.getShowTime(), show
                  .getAvailableSeats
());
        
}
        
return items;
     
}
     
catch (FinderException e) {
        
throw new EJBException(e);
     
}
   }
}
// /:~

Метод использует поисковик findAll(…) из домашнего интерфейса для получения всех показов, а для каждого показа он создает объект ShowItem, который хранится в возвращаемом массиве. Также, поскольку ShowItem содержит MovieItem, для каждого показа метод запрашивает ассоциированные фильмы с помощью CMP поля фильма (вызывается метод getMovie( )) и создается MovieItem по полученной информации.

Других модификаций кода EJB в example11 нет. Однако, клиентское приложение должно быть полностью переписано, и теперь оно должно быть сопряжено с графическим пользовательским интерфейсом, который вы сможете использовать для создания и удаления фильмов и показов в системе. Хотя способности клиентского приложения минимальны, вы можете пожелать поиграть с ним или, может быть, использовать его, как исходную точку для реализации дополнительных возможностей, таких как поиск показа определенного фильма, или поиск показа по определенному времени показа. Это также потребует, чтобы вы добавили больше возможностей в façade сессионный компонент и, возможно, в сущностный компонент, но это будет хорошим упражнением.