[an error occurred while processing this directive] IT • archiv :: Print

IT • archiv


[an error occurred while processing this directive]

[an error occurred while processing this directive]

Управляемые контейнером связи в 21-м веке

[an error occurred while processing this directive](none) [an error occurred while processing this directive](none)[an error occurred while processing this directive] ::
[an error occurred while processing this directive](none)
[an error occurred while processing this directive]([an error occurred while processing this directive] Ален Фоглесон [an error occurred while processing this directive])

[an error occurred while processing this directive](none)

Новая служба управления связями в EJB 2.0 помогает разработчикам в быстром развёртывании.

Enterprise JavaBeans
PDF versionPDF версия
Обзор
Определение постоянства, управляемого контейнером (Container-Managed Persistence — CMP), в спецификации корпоративных компонент (Enterprise JavaBeans — EJB) версии 2.0 позволяет тонко управлять связями между постоянными компонентами. Контейнер может также сохранять такие связи вместо того, чтобы автор компонента контролировал их. В этой статье Аллен Фоглесон (Allen Fogleson) объясняет и демонстрирует, как использовать связи в постоянных компонентах. (В оригинальной версии на английском языке 3000 слов)

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

К счастью, разработчики спецификации корпоративных компонент, осознав проблему, добавили управляемое контейнером постоянство во вторую версию спецификации (EJB 2.0).

Что же такое отношения? Как вы описываете их? Как вы закодируете их в своих Java классах? В этой статье даются ответы на эти и другие вопросы.

Замечание
Будем считать, что вы знакомы с Java и технологией корпоративных компонент (EJB). Несмотря на то, что в статье приведён обзор постоянства, управляемого контейнером (CMP2), вы не найдёте здесь всеобъемлющего описания этой технологии. Смотрите Ресурсы содержащие ссылки на статьи по EJB и CMP.
Замечание
Вы можете скачать исходные коды примеров статьи по ссылке в Ресурсах.

Обзор постоянства, управляемого контейнером (CMP 2)

Перед тем, как разбираться со связями в корпоративных компонентах (EJB), вы должны понять, как корпоративные компоненты работают. Как определяются их постоянные поля? Как вы определяете связи? Какие объекты получаются при обращении по связи?

Спецификации корпоративных компонент версий 1.0 и 1.1 определяли управляемые контейнером поля размещением атрибутов в компоненте, с последующим описанием их в дескрипторе развёртывания. Хотя остальные объектные компоненты могут храниться с родительским объектным компонентом, контейнер автоматически не управляет их постоянством или загрузкой. Разработчик компонента, теоретически, пишет дополнительный код, обычно в ejbCreate(), ejbActivate() и ejbPassivate() методах для обработки этих дополнительных объектных компонентов. Разработчик, в качестве альтернативы, может определять объектные компоненты используя управляемое компонентом постоянство.

Спецификация корпоративных компонент версии 2.0 изменила ситуацию драматическим образом. Спецификация претерпела множество изменений, управляемые контейнером поля больше не определяются в компоненте посредством атрибутов; вместо этого для их определения используются абстрактные методы получения и изменения значения (getters, setters). Через методы получения и изменения значения вы можете также обращаться к другим объектным компонентам. При получении значения тип возвращаемого объекта зависит от вида зависимости. Если возвращается единственная сущность связанного компонента, то метод возвращает локальный интерфейс этого компонента. При возврате множества объектов связанных компонентов метод возвращает объект типа java.util.Collection или java.util.Set. Кроме того, разработчик компонента может описать отношение, определённое в элементе отношения дескриптора развёртывания, в дескрипторе развёртывания корпоративных компонент. Рассмотрим пример:

...
<relationships>

  <ejb-relation>

    <ejb-relation-name>
	User-Demographics</ejb-relation-name>
    <ejb-relationship-role>
      <ejb-relationship-role-name>
	User-Demographics</ejb-relationship-role-name>
      <multiplicity>One</multiplicity>
      <relationship-role-source>
        <ejb-name>Users</ejb-name>
      </relationship-role-source>
      <cmr-field>
        <cmr-field-name>demographics</cmr-field-name>
      </cmr-field>
    </ejb-relationship-role>
    <ejb-relationship-role>
      <ejb-relationship-role-name>
	Demographics-belongs-to-Users</ejb-relationship-role-name>
      <multiplicity>One</multiplicity>
      <cascade-delete />
      <relationship-role-source>
        <ejb-name>Demographics</ejb-name>
      </relationship-role-source>
    </ejb-relationship-role>
  </ejb-relation>
</relationships>
...

Приведённый пример определяет компонент User с однонаправленной связью типа "один к одному" с компонентом Demographics. Давайте дальше разберём по отдельности каждый элемент XML дескриптора развёртывания.

Элемент relationships сообщает контейнеру, что вы описываете связи между компонентами, размещёнными в данном дескрипторе. Каждая связь начинается с элемента ejb-relation. Элемент ejb-relation-name содержит текстовое описание связи. Элемент ejb-relationship-role, встречающийся дважды в любом ejb-relation, описывает роль каждого компонента в этой связи. Каждый ejb-relationship-role содержит элемент с текстовым описанием ejb-relationship-role-name и описывает роль данного компонента в отношении. Элемент multiplicity описывает, находится ли компонент на стороне одинарного или же множественного отношения. Элемент relationship-role-source содержит ejb-name, который определяет объектный компонент, участвующий в отношении. Элемент ejb-name должен совпадать с одним из компонентов, определённых в файле EJB jar. Элемент cmr-field является необязательным. Если описывается компонент, не имеющий полей отношений управляемых контейнером, то cmr-field не используется, как это было продемонстрировано во втором элементе ejb-relationship-role в приведённом ранее примере. Тем не менее, вы должны иметь элемент cmr-field для компонентов, имеющих поля отношений управляемые контейнером. Текст внутри элемента содержит элемент cmr-field-name, описывающий поле управляемого контейнером отношения. Наконец, элемент cascade-delete, включённый в компонент, являющийся в отношении дочерним, сообщает контейнеру, что если родитель отношения удалён, то должны быть удалены и все его потомки.

Обзор отношений

Новая схема постоянства во второй версии спецификации корпоративных компонент (EJB 2.0) может моделировать восемь типов отношений:

В приведённом выше списке, вы видите три основных вида отношений: "один к одному", "один ко многим" , "многие ко многим". Мы изучим все их более подробно. Для начала, однако, давайте изучим, как вторая версии спецификации корпоративных компонент (EJB 2.0) управляет такими отношениями.

Локальные интерфейсы

Как было упомянуто ранее, объектный компонент моделирует каждый тип отношений используя абстрактные методы доступа. Возвращаемое значение имеет тип либо локального интерфейса, либо коллекции локальных интерфейсов. Несмотря на то, что локальные интерфейсы не уникальны для объектных компонентов, они используются в создании отношений, что ограничивает их полезность. С тех пор, как отношения используют локальные интерфейсы, которые могут работать только внутри одной и той же виртуальной машины(VM), использование отношений возможно только внутри одной виртуальной машины. Теоретически, вы не можете создать отношения с другими распределёнными объектными компонентами. Хотя такое ограничение препятствует использованию многих возможностей, оно даёт одну большую выгоду: локальные интерфейсы крайне быстры. Поскольку исчезают затраты на удалённые вызовы, вызовы локальных интерфейсов осуществляются практически так же, как и другие локальные вызовы методов обычных Java классов.

Отношения типа один к одному

Теперь, когда вы понимаете основы отношений в EJB, давайте смоделируем ваше первое отношение. Для большей простоты, начнём с простого отношения типа один к одному: рассмотрим отношение между пользователем и адресом. Поскольку рассматривается упрощённый случай, покупатель и адрес связанны однонаправленным отношением типа один к одному, которое моделируется при помощи одного компонента покупателя и одного компонента адреса. Объектный компонент реализован простым, чтобы сосредоточиться на определении отношения.

Начнём с создания абстрактного класса, определяющего компонент пользователя. Хотя представленный объектный компонент мог бы работать, он упрощён для демонстрации того, как работают отношения. В реальных системах вы не будете создавать объектные компоненты с таким первичным ключом:

package relationships;
import javax.ejb.*;
import java.math.*;
import relationship.*;
public abstract class CustomerBean implements EntityBean {
   // Первичный ключ
   public abstract String getCustomerName();
  
    // Управляемые контейнером поле отношения.
   public abstract Address getAddress();
   public abstract void setAddress(Address addr);
   ...
}

Обратите внимание, что поле, хранящее значение отношения, объявлено как простое управляемое контейнером поле объектного компонента. Вы просто объявляете абстрактное поле, имеющее тип локального интерфейса связанного компонента. Класс, реализующий компонент Address оказывается проще чем класс CustomerBean, т.к. не содержит полей отношения:

package relationships;
import javax.ejb.*;
import java.math.*;
import relationship.*;
public abstract class AddressBean implements EntityBean {
  // Наш первичный ключ
  public abstract String getAddress();
  ...
}

Вы ничего не делаете в объектных компонентах для определения отношения. Истинная мощь отношений, однако, заключается в том, что они могут быть однонаправленными, двунаправленными, один к одному, один ко многим или многие ко многим. Хотя вы можете из этого примера определить всё, что нужно для отношений типа один к одному, вы всё же должны описать отношение в дескрипторе развёртывания. Фрагмент дескриптора развёртывания должен выглядеть примерно так:

...
<relationships>
  <ejb-relation>
    <ejb-relation-name>
	Customer contains an address</ejb-relation-name>
    <ejb-relationship-role>
      <ejb-relationship-role-name>
 	Customer-Address</ejb-relationship-role-name>
      <multiplicity>One</multiplicity>
      <relationship-role-source>
        <ejb-name>Customer</ejb-name>
      </relationship-role-source>
      <cmr-field>
        <cmr-field-name>address</cmr-field-name>
      </cmr-field>
    </ejb-relationship-role>
    <ejb-relationship-role>
      <ejb-relationship-role-name>
	Address-belongs-to-Customer</ejb-relationship-role-name>
      <multiplicity>One</multiplicity>
      <cascade-delete />
      <relationship-role-source>
        <ejb-name>Address</ejb-name>
      </relationship-role-source>
    </ejb-relationship-role>
  </ejb-relation>
</relationships>
...

Как вы видите, моделирование однонаправленного отношения типа один к одному достаточно просто. Теперь вы можете получать информацию о пользователе и его адрес из компонента пользователя. Теперь предположим, что ваш деловой партнёр хочет получать демографическую информацию поиском пользователя на основе его расположения. Для этого вы должны написать соответствующую логику в объектном компоненте Address. Используя отношения, управляемые контейнером, вы можете быстро разработать нужную логику с минимальными изменениями объектного компонента. В компонент Address добавим одно управляемое контейнером поле:

...
  // Управляемое контейнером поле, хранящее пользователя
   public abstract Customer getCustomer();
  public abstract void setCustomer(Customer customer);
...

Заметим, что в класс был добавлен лишний метод установки значения. Его использование зависит от бизнес требований приложения. А именно, если вы никогда не создаёте пользователей исходя из адреса, не включайте метод установки значения и используйте метод получения значения для поиска пользователей на основе адреса.

Вы также должны немного изменить дескриптор размещения для изменения типа отношения на двунаправленное:

...
      <multiplicity>One</multiplicity>
      <relationship-role-source>
        <ejb-name>Address</ejb-name>
      </relationship-role-source>
      <cmr-field>
        <cmr-field-name>customer</cmr-field-name>
      </cmr-field>
...

Изменив две строки кода (без учёта любых дополнительных поисковых или бизнес методов) и три строки в дескрипторе развёртывания, вы теперь можете искать пользователей по их адресу, или искать адреса, принадлежащие конкретному пользователю. Фактически, если вы использовали только отношение типа один к одному, вы, тем не менее, разрешили многие из проблем разработки.

Отношения типа один ко многим и многие к одному

Несмотря на то, что отношения один к одному доказали свою полезность, они не смогут полностью удовлетворить ваши потребности. Вы обнаружите, что отношения типа один ко многим, более полезны, чем отношения типа один к одному. В самом деле, вы можете расширить пример с пользователем и адресом до связи один ко многим, если захотите хранить множество адресов, таких как адрес доставки, выставления счетов, домашний и рабочий.

Как другой пример отношения один ко многим, можно рассматривать пользователя и информацию о его телефонах, для чего создадим простой компонент телефона:

package relationships;
import javax.ejb.*;
import java.math.*;
import relationship.*;
public abstract class Phone implements EntityBean {
  // Номер телефона
   public abstract String getPhoneNumber();
  // Тип телефона
  public abstract String getPhoneType();
  public abstract void setPhoneType(String);
...

Компонент даёт вам возможность задать номер и тип телефона. С разработанным компонентом телефона вы можете теперь определить отношение с компонентом CustomerBean. Так как оба класса Collection и Set подходят для хранения множества экземпляров объектных компонент, вы можете выбрать любой из них как тип для абстрактного поля в CustomerBean.

Как выбрать? Класс Collection содержит все объектные компоненты в отношении, независимо от их уникальности. В противоположность, класс Set содержит только уникальные связанные объектные компоненты. Поскольку телефонный номер является первичным ключом компонента, уникальность является свойством компонента, так что мы можем использовать объект Collection как возвращаемый из CustomerBean. Изменим компонент так, чтобы он содержал поле телефона с управляемой контейнером связью:

package relationships;
import java.util.Collection;
import javax.ejb.*;
import java.math.*;
import relationship.*;
public abstract class CustomerBean implements EntityBean {
   // Первичный ключ
    public abstract String getCustomerName();
  
    // Поле управляемого контейнером отношения
   public abstract Address getAddress();
   public abstract void setAddress(Address addr);
   // Поле управляемого контейнером отношения для телефонов
    public abstract Collection getPhone();
   public abstract void setPhone(Collection phones);
...
}

Имея готовый компонент, вы можете теперь описать отношение в дескрипторе развёртывания, чтобы контейнер мог правильно управлять им. Для этого добавьте ещё один элемент ejb-relation в дескриптор развёртывания:

<relationships>
...
  <ejb-relation>
    <ejb-relation-name>
	Customer contains many phones</ejb-relation-name>
    <ejb-relationship-role>
      <ejb-relationship-role-name>
	Customer-Phone</ejb-relationship-role-name>
      <multiplicity>One</multiplicity>
      <relationship-role-source>
        <ejb-name>Customer</ejb-name>
      </relationship-role-source>
      <cmr-field>
        <cmr-field-name>phone</cmr-field-name>
      </cmr-field>
    </ejb-relationship-role>
    <ejb-relationship-role>
      <ejb-relationship-role-name>
	Phones-belongs-to-Customer</ejb-relationship-role-name>
      <multiplicity>Many</multiplicity>
      <cascade-delete />
      <relationship-role-source>
        <ejb-name>Phone</ejb-name>
      </relationship-role-source>
    </ejb-relationship-role>
  </ejb-relation>
...
</relationships>

Здесь мы описали однонаправленное отношение типа один ко многим.

Так же, как и в случае отношения один к одному, вы можете легко превратить отношение типа один ко многим в двунаправленное отношение, добавив поле управляемого контейнером отношения в класс PhoneBean. Вы также должны немного подкорректировать дескриптор развёртывания для того, чтобы сделать отношение двунаправленным. Сначала модифицируем класс PhoneBean:

...
  // Управляемое контейнером поле, хранящее пользователя customer
  public abstract Customer getCustomer();
  public abstract void setCustomer(Customer customer);
...

Вы должны были узнать приведённый выше код — он идентичен тому, как вы модифицировали AddressBean для двунаправленного поведения. Дескриптор развёртывания будет такой же, как и в примере с адресами, за исключением изменения в описании отношения компонента телефона. Теперь вы можете найти пользователя по любому из его адресов, или вы можете найти все адреса для заданного пользователя.

Отношения типа многие ко многим

Несмотря на то, что вы не увидите последний тип отношений — многие ко многим так же часто, как два других типа, иногда это может быть критично для приложения. Для иллюстрации отношения многие ко многим будет рассмотрен замечательный пример. Давайте предположим, что вы разработали веб-сайт, страницы которого сохранены в базе данных. Каждый раз, когда пользователь посещает станицу, вы хотите знать, является ли заход этого пользователя повторным. Для решения этой задачи, вы расширяете рассмотренный CustomerBean так, чтобы он смог так же описывать посетителей сайта:

package relationships;
import java.util.Collection;
import javax.ejb.*;
import java.math.*;
import relationship.*;
public abstract class CustomerBean implements EntityBean {
   // Первичный ключ
    public abstract String getCustomerName();
  
    // Управляемое контейнером поле отношения
   public abstract Address getAddress();
   public abstract void setAddress(Address addr);
   // Управляемое контейнером поле отношения для телефона
    public abstract Collection getPhone();
   public abstract void setPhone(Collection phones);
   // Управляемое контейнером поле
   // отношения для посещений веб-страниц visits
   public abstract Collection getWebPages();
   public abstract void setWebPages(Collection pages);
...
}

Снова, вы применяете коллекцию, потому что вы будете возвращать множество экземпляров веб-страницы для каждого пользователя. Далее, определим класс WebpageBean для использования в дескриптор развёртывания:

package relationships;
import java.util.Collection;
import javax.ejb.*;
import java.math.*;
import relationship.*;
public abstract class WebpageBean implements EntityBean {
   // Публичный ключ
    public abstract String getPageName();
...
}

Несмотря на то, что мы имеем компонент с одним хитро разработанным полем, это всё, что необходимо для демонстрации отношения, так что давайте добавим соответствующее отношение в дескриптор развёртывания:

<relationships>
...
  <ejb-relation>
    <ejb-relation-name>
	Many Customers contain many webpages</ejb-relation-name>
    <ejb-relationship-role>
      <ejb-relationship-role-name>
	Customer-Pages</ejb-relationship-role-name>
      <multiplicity>Many</multiplicity>
      <relationship-role-source>
        <ejb-name>Customer</ejb-name>
      </relationship-role-source>
      <cmr-field>
        <cmr-field-name>webPages</cmr-field-name>
      </cmr-field>
    </ejb-relationship-role>
    <ejb-relationship-role>
      <ejb-relationship-role-name>
	Address-belongs-to-Customer</ejb-relationship-role-name>
      <multiplicity>Many</multiplicity>
      <relationship-role-source>
        <ejb-name>Webpage</ejb-name>
      </relationship-role-source>
    </ejb-relationship-role>
  </ejb-relation>
...
</relationships>

Обратите внимание на отличие в дескрипторе развёртывания: вы больше не добавляете элемент cascade-delete. Почему? Если вы будете делать каскадное удаление и удалите одного пользователя, то вы удалите все веб-страницы из базы данных, оставаясь со множеством пользователей, которые никогда не посещали ни одной страницы.

Теперь у вас есть компонент CustomerBean, который может находить все веб-страницы, которые посетил пользователь. Однако, как только вы закончите, выш босс попросит у вас некоторую статистическую функциональность. Он хочет знать для каждой страницы всех пользователей, которые посетили эту страницу. Вооружённые приобретёнными навыками в построении связей между компонентами, вы добавите управляемое контейнером поле отношения в WebpageBean:

...
    // Пользователь, который нас посетил
     public abstract Collection getCustomers();
    public abstract void setCustomers(Collection customers);
...
}

Вы также измените дескриптор развёртывания, чтобы компонент Webpages так же включал поле отношения:

<relationships>
...
  <ejb-relation>
    <ejb-relation-name>
	Many Customers contain many webpages</ejb-relation-name>
    <ejb-relationship-role>
      <ejb-relationship-role-name>
	Customer-Pages</ejb-relationship-role-name>
      <multiplicity>Many</multiplicity>

      <relationship-role-source>
        <ejb-name>Customer</ejb-name>
      </relationship-role-source>
      <cmr-field>
        <cmr-field-name>webPages</cmr-field-name>
      </cmr-field>
    </ejb-relationship-role>
    <ejb-relationship-role>
      <ejb-relationship-role-name>
	Address-belongs-to-Customer</ejb-relationship-role-name>
      <multiplicity>Many</multiplicity>
      <relationship-role-source>
        <ejb-name>Webpage</ejb-name>
      </relationship-role-source>
      <cmr-field>
        <cmr-field-name>customers</cmr-field-name>
      </cmr-field>

    </ejb-relationship-role>
  </ejb-relation>
...
</relationships>

После перекомпиляции ваш объектный компонент может получать всех пользователей для любой страницы, или все посещённые страницы для любого пользователя.

Обзор отношений

Даже простые примеры из этой статьи демонстрируют, что управляемые контейнером отношения подтверждают свои полезные для разработчиков свойства. Разработчики приложений теперь могут проектировать и реализовывать сложные отношения между объектными компонентами, не прибегая к индивидуальной логике в своих классах, реализующих объектные компоненты. Вы можете иметь отношения типа один к одному, один ко многим или многие ко многим не беспокоясь о состоянии объектных компонент. Теперь контейнер гарантирует корректное постоянство, активацию и загрузку дочерних компонентов. Спецификация корпоративных компонент изначально ориентировалась на исключение у разработчика приложения необходимости управлять логикой реализации транзакций и постоянства. Новые возможности построения отношений во второй спецификации корпоративных компонент (EJB 2.0) делают большой шаг, необходимый для реального освобождения разработчика от деталей постоянства объектных компонент.

Об авторе

Ален Фоглесон (Allen Fogleson), ведущий разработчик в Cross Media Marketing, работает в индустрии информационных технологий 16 лет, за это время выступал во множестве ролей, включая разработчика, ведущего разработчика, менеджера проектов и консультанта по информационным технологиям. Последние 5 лет Ален занимался проектированием и разработкой приложений масштаба предприятия используя Java технологии, в последние 3 года специализируясь только на проектах с использованием технологии корпоративных компонент (EJB).

Ресурсы

Reprinted with permission from the April 2002 edition of JavaWorld magazine. Copyright © ITworld.com, Inc., an IDG Communications company.
View the original article at: http://www.javaworld.com/javaworld/ jw-04-2002/jw-0419-cmp.html

[an error occurred while processing this directive]
[an error occurred while processing this directive] Перевод на русский © Олег Лапшин, 2003
< Вернуться на caйт :: Copyright © 1999 — 2010, IT • archiv.