Продолжается подписка на наши издания! Вы не забыли подписаться?

Entity-компоненты. Нужны ли они?**

Александр Цимбал

При общении с разработчиками, использующими технологию EJB, довольно часто возникает вопрос о разумности и эффективности использования entity-компонентов. Поскольку обсуждаемая тема может быть интересна и программистам, только начинающим использовать технологии EJB и J2EE, имеет смысл остановиться на том, что же из себя представляют entity-компоненты.

Что такое entity-компонент

Понятие «entity-компонент» может относиться к двум различным вещам:

Существует еще и понятие «экземпляра entity-компонента». Это просто переменная языка Java. Технология EJB полностью «отделяет» программиста от экземпляра компонента. Программист может влиять на число экземпляров компонента, находящихся в EJB-контейнере, только косвенно. Кроме того, программист никогда непосредственно не обращается к экземпляру компонента. Можно с некоторой натяжкой сказать, что особенности реализации и цикла жизни экземпляров компонентов программиста не интересуют – он имеет дело с компонентами (во втором смысле), а не с их экземплярами.

Рассмотрим некоторую реляционную базу данных, содержащую таблицу с 1000 записей. В первом смысле можно сказать, что программист создает один entity-компонент – для работы с любой из записей этой таблицы. Особенности этого компонента как типа данных однозначно определяются именем и структурой таблицы, URL базы данных и бизнес-логикой системы, и не зависят от количества записей в таблице.

В то же время можно говорить (во втором смысле нашего определения компонента), что созданы 1000 entity-компонентов, каждый из которых предназначен для работы с отдельной записью из таблицы. Эти компоненты начинают существовать в тот момент, когда принимается решение, что к данным в таблице желательно обеспечить доступ с помощью технологии EJB с использованием entity-компонентов и что каждый компонент служит для представления информации ровно из одной записи. При этом не важно, существует уже или нет хоть один экземпляр компонента, установлен ли EJB-компонент (как тип данных) в контейнер EJB и даже то, запущен ли в данный момент контейнер EJB вообще. Другими словами, наш entity-компонент в такой трактовке – чисто логическое понятие.

Ничего определенного о количестве экземпляров наших entity-компонентов в этой ситуации сказать нельзя – все зависит от особенностей реализации конкретного контейнера EJB и параметров его настроек.

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

Структура entity-компонента

Entity-компонент имеет много общего с session-компонентом. Как минимум, для него нужно написать класс компонента, определить home- и remote-интерфейсы, а также дескриптор развертывания – в виде XML-документа.

С принятием спецификации EJB 2.0 терминология немного изменилась: было введено понятие локальных интерфейсов, т.е. чистых интерфейсов Java, которые не обязаны удовлетворять требованиям RMI/IIOP и, следовательно, годятся только для выполнения локальных (в пределах одной виртуальной машины Java) вызовов. В связи с этим, термин «remote-интерфейс» из EJB 1.1 был заменен на «component-интерфейс» в EJB 2.0. Клиентами всех видов вызываются именно методы component-интерфейса. Существует два вида component-интерфейсов (как и home-интерфейсов) – local (вызовы в пределах одной JVM) или remote (удаленные вызовы).

Принципиальным отличием entity-компонента от session-компонента является наличие в классе компонента поля (или полей), которые хранят значение, позволяющее отличить данный entity-компонент от других. Это поле выполняет роль ключевого поля в реляционных базах данных. В общем случае необходимо определить новый тип данных для этого поля (создать новый класс Java). Этот класс называется «PrimaryKey-классом». Естественно, новый класс нужно создавать не всегда – в качестве primaryKey-класса можно использовать стандартные классы Java, например, java.lang.String или java.lang.Integer.

Интересным является следующий случай: контейнер создал экземпляр entity-компонента, он находится в памяти, но при этом контейнер не сопоставил этот экземпляр ни с какими данными в базе данных (например, с конкретной записью). Обычно это выражается в том, что «ключевое» поле в классе компонента имеет значение null. Очевидно, что несколько таких экземпляров нельзя отличить друг от друга. В таком случае говорят, что данный экземпляр «не идентифицирован (не имеет identity)». Как правило, EJB-контейнер создает пул таких экземпляров. Первый попавшийся из них сопоставляется с данными в базе данных в момент, когда клиент выдает одну из create- или find-команд home-интерфейса.

Представление и цикл жизни entity-компонента

Ниже приведены две диаграммы (они взяты из спецификации EJB 2.0). Первая показывает, как выглядит и «живет» entity-компонент с точки зрения клиента (рисунок 1).

Важно подчеркнуть следующие обстоятельства:

Вторая диаграмма показывает цикл жизни entity-компонента (т.е. различные этапы взаимодействия entity-компонента, клиента и контейнера EJB) (Рисунок 2)

В состоянии «pooled» находятся entity-компоненты (здесь правильнее говорить не о компонентах, а об их экземплярах), с которыми еще не сопоставлен identity. Размер этого пула является настраиваемым параметром для всех промышленных серверов приложений.

Второе возможное состояние компонента – состояние «ready». Переход в него выполняется либо в результате вызова клиентом одного из create-методов home-интерфейса компонента, либо по команде контейнера EJB (которая сопровождается вызовом callback-метода ejbActivate()).

В состоянии "ready" компонент сопоставлен с конкретным фрагментом данных в БД (например, с некоторой записью). При этом его ключевое поле получает определенное значение. Если наш entity-компонент сопоставлен с записью из таблицы реляционной СУБД, то ключевое поле этого компонента (любого его экземпляра), скорее всего, получит значение, соответствующее ключевому полю в этой записи.

Когда компонент находится в состоянии «ready», контейнер (или клиент) могут произвольное число раз вызывать методы ejbLoad() и ejbStore(), определенные в классе компонента. Метод ejbLoad() логически эквивалентен SQL-оператору SELECT, а метод ejbStore() – оператору UPDATE. Спецификация EJB не оговаривает строго все случаи, когда контейнер выполняет вызов этих методов. Здесь все зависит от конкретной реализации.

Для чего придуманы entity-компоненты

Entity-компоненты решают, главным образом, четыре задачи:

Надо также иметь в виду, что доступ к EJB-компонентам могут получить любые клиентские приложения, использующие CORBA 2.3 и старше. Это обстоятельство может быть очень важным при наличии большого количества ранее написанных на C++, Delphi (или других языках) клиентских приложений.

Рассмотрим немного подробнее каждый из приведенных пунктов.

Объектное представление реляционных данных

EJB (совместно с JDBC) предлагает схему отображения как типов данных, характерных для реляционных СУБД, в их Java-эквиваленты, так и связей и ограничений на уровне СУБД в соотношения между Java-классами. Таблица соответствий типов данных приведена в спецификации JDBC, которую можно найти, например, по адресу http://java.sun.com/proucts/jdbc/download.html#corespec30.

Программист определяет EJB entity-компонент как класс (или несколько связанных классов), который (или которые) содержат поля, соответствующие полям из одной или нескольких записей из одной или нескольких таблиц JDBC-базы данных.

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

Гибкость объектно-реляционного отображения в режиме CMP существенно отличается для EJB версий 1.1 и 2.0. Хотя спецификация EJB 2.0 уже принята, программные продукты, реализующие эту технологию, появились только недавно. Например, EJB 2.0 позволяет (в отличие от EJB 1.1) учитывать наличие связей между таблицами с помощью удаленных ключей (foreign keys).

Следует также иметь в виду следующее очевидное обстоятельство: представление информации из БД в объектном виде – конкретно, в виде классов и интерфейсов Java – легко позволяет создать иерархическую структуру entity-компонентов c использованием наследования в классическом стиле ООП. Это позволяет не только эффективно развивать прикладную программу при изменении представления данных в БД, но и создать чисто логический уровень доступа к данным, причем часть бизнес логики системы может быть реализована с помощью EJB, а часть – каким-то другим образом. Впрочем, такой подход получил большое распространение применительно к EJB 1.1, в котором все обращения компонентов друг к другу были «удаленными» - с использованием RMI/IIOP – и, следовательно, весьма дорогостоящими с точки зрения затрат времени. В EJB 2.0 можно предусмотреть в дополнение к remote-интерфейсам (или вместо них) локальные высокоэффективные интерфейсы.

Еще одно интереснейшее новшество EJB 2.0 – с точки зрения объектного представления данных – это создание спецификации языка объектных запросов (QL, Query Language), используемого вместе с entity-компонентами в режиме CMP. QL позволяет формировать запросы, внешне очень похожие на SQL-операторы SELECT, но в которых фигурируют не реальные элементы из конкретных баз данных, а поля EJB-компонентов и дополнительная информация, необходимая для формализации связей между entity-компонентами (например, отношения между сущностями). Контейнер может «откомпилировать» такой оператор в эквивалентный ему SQL-оператор SELECT, а может поступить и каким-то другим образом.

Кеширование данных

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

Экземпляры Entity-компонентов и играют роль вспомогательного кэша. Нужно это или нет – решать программисту в каждом конкретном случае.

Важным для принятия решения о необходимости или желательности использования entity-компонентов является знание особенностей реализации выбранного сервера приложений. В общем случае лучше предполагать, что кэшированное объектное представление данных из БД доступно одному клиенту. Для разделяемого одновременного доступа к кэшированной информации со стороны нескольких клиентов entity-компоненты не предназначены (хотя такой режим работы возможно организовать для многих реализаций серверов приложений – например, в WebLogic). Любая качественная реализация сервера приложений содержит большое количество параметров настройки, которые влияют на производительность системы с использованием entity-компонентов. Знание этих параметров настройки и их грамотное использование – непременное условие создания эффективных систем.

Например, в Borland AppServer (новое название – Borland Enterprise Server) предусмотрены следующие параметры настройки (приведены, естественно, только наиболее важные):

ejb.transactionCommitMode – определяет режим взаимодействия entity-компонента с транзакциями. Возможные значения:

ejb.maxBeansInCache – позволять задать максимальное число entity-компонентов, работающих в режиме ejb.transactionCommitMode=A, состояние которых хранится в кэше.

ejb.cmp.optimisticConcurrencyBehavior – определяет режим сохранения внесенных изменений для entity-компонентов, работающих в режиме CMP. Возможные значения:

UPDATE set FIELD_VALUE = :NewValue WHERE FIELD_VALUE = :OldValue

где NewValue – новое значение (вносимое в БД), а OldValue – значение, считанное из этого поля в момент загрузки состояния из БД в экземпляр EJB-компонента.

Сигналы о необходимости выполнения синхронизации состояния кэша и БД вырабатываются контейнером EJB на основании информации о состоянии транзакций. Именно поэтому для entity-компонентов (в отличие от session-компонентов) запрещен режим ВMT – Bean Managed Transactions. Транзакциями для entity-компонентов всегда управляет контейнер.

Гибкость настройки программ

Уже говорилось о схемах объектно-реляционного отображения, предлагаемого технологией EJB. Важнейшей особенностью такой схемы отображений является возможность задания ее в декларативном виде. Это означает, что программист формально задает логические соотношения между SQL-конструкциями и Java-классами. Реальное сопоставление выполняется не на стадии написания программы, а на стадии ее настройки для каждого конкретного заказчика – без перекомпиляции кода EJB-компонентов. Разумеется, речь идет главным образом для entity-компонентов в режиме CMP.

Гибкость настройки обеспечивается за счет двух основных решений:

Использование режима CMP приводит к тому, что программист может создавать entity-компоненты – как средство чтения и изменения информации в JDBC-БД – не зная ни JDBC API, ни того, какая конкретно база данных будет использована конкретным заказчиком. В коде компонента в явном виде присутствует только реализация бизнес-логики системы применительно к использованию информации из некоторой СУБД.

Обеспечение конкурентного доступа к данным и автоматическое управление транзакциями

Универсальный доступ к данным (в описанном в предыдущем пункте стиле) тесно связан с управлением транзакциями. О различных режимах транзакций и взаимодействии мониторов транзакций с JDBC в рамках технологии J2EE рассказано в статье «Управление транзакциями в CORBA и EJB». Здесь достаточно сказать следующее: разработчику entity-компонента нет необходимости знать о том, как именно происходит управление транзакциями. Более того, разработчик компонента на стадии написания кода этого компонента даже не может знать, будут ли эти транзакции локальными или глобальными, будет ли процесс их завершения однофазным или двухфазным, используется ли его компонент в распределенных транзакциях или нет. За это отвечает т.н. deployer (в терминах технологии EJB) – т.е. специалист, который выполняет настройку готовой системы для конкретного заказчика. Deployer имеет дело не с Java-компонентами, а с их XML-дескрипторами.

Обеспечение конкурентного доступа к данным полностью берет на себя контейнер. Существует два подхода к решению этой задачи:

Клиент для entity-компонентов

При использовании EJB-компонентов стандарта 1.1 вызов клиентом любого из методов как home-, так и remote-интерфейса, является дорогостоящим с точки зрения затрат времени. Это относится ко всем видам клиентов: не важно, кто вызывает бизнес-метод – клиентское приложение, апплет, сервлет или другой EJB-компонент. Тем не менее, даже в таком варианте существовала разница при выполнении действительно удаленного вызова (с передачей параметров по сети) и вызова «псевдоудаленного» – например, когда компонент-клиент и компонент-сервер расположены в одном контейнере и, следовательно, работают под управлением одной виртуальной машины Java.

Появление в EJB 2.0 локальных home- и component-интерфейсов еще более повысило важность разработки правильной архитектуры создаваемых систем, так как скорость выполнения удаленных вызовов и вызовов локальных различаются уже на порядки.

Для повышения производительности создаваемых систем применительно к использованию entity-компонентов желательно следовать нескольким рекомендациям:

BMP и CMP-режимы для entity-компонентов

Естественно, возникает вопрос: когда разумно использовать BMP, а когда – CMP-режим работы entity-компонентов?

Надо понимать, что не всегда этот выбор возможен. Например, вы хотите использовать QL EJB 2.0. Это означает, что вы должны работать в режиме CMP. С другой стороны, если вы хотите работать с БД, для которой просто нет JDBC-интерфейса (но есть другой Java API), то безальтернативным вариантом становится выбор режима BMP.

Но в большинстве случаев возможность выбора есть. Как быть?

В общем случае рекомендуется по возможности использовать режим CMP из-за двух его преимуществ:

В качестве примера типичного кода, который может находиться в методах entity-компонента, работающего в режиме CMP, приведем код, который генерируется мастером Borland JBuilder:

public void ejbLoad() throws RemoteException
{
  mkey = ((Integer) entityContext.getPrimaryKey()).intValue();
  Connection connection = null;
  PreparedStatement statement = null;
  try
  {
    connection = dataSource.getConnection();
    statement = connection.prepareStatement ("SELECT STRING_FIELD FROM
              MASTERT_EJB WHERE MKEY = ?");
    statement.setInt(1, mkey);
    ResultSet resultSet = statement.executeQuery();
    if (!resultSet.next())
    {
      throw new NoSuchEntityException("Row does not exist");
    }
    this.stringField = resultSet.getString(1);
  }
  catch(SQLException e)
  {
    throw new EJBException ("Error executing SQL SELECT STRING_FIELD
              FROM MASTERT_EJB WHERE MKEY = ?: " + e.toString());
  }
  finally
  {
    closeConnection(connection, statement);
  }
  :
}

public void ejbLoad() throws RemoteException 
{
  mkey = ((Integer) entityContext.getPrimaryKey()).intValue();
  Connection connection = null;
  PreparedStatement statement = null;
  try 
  {
    connection = dataSource.getConnection();
    statement = connection.prepareStatement ("SELECT STRING_FIELD FROM 
                                              MASTERT_EJB WHERE MKEY = ?");
    statement.setInt(1, mkey);
    ResultSet resultSet = statement.executeQuery();
    if (!resultSet.next()) 
    {
      throw new NoSuchEntityException("Row does not exist");
    }
    this.stringField = resultSet.getString(1);
  }
  catch(SQLException e) 
  {
    throw new EJBException ("Error executing SQL SELECT STRING_FIELD 
                       FROM MASTERT_EJB WHERE MKEY = ?: " + e.toString());
  }
  finally 
  {
    closeConnection(connection, statement);
  }
 ...
}

Как видно, при вызове метода происходит получение значения ключевого поля с помощью стандартного метода getPrimaryKey(), затем компонент получает логическое соединение с БД из пула соединений, а затем выполняются классические обращения к JDBC API с проверкой корректности завершения и возбуждением (если необходимо) соответствующих исключений. Ясно, что ни одно из этих выполняемых действий не является излишним.

Использование entity-компонентов и JDBC API

В этом разделе будет проведено некоторое сравнение использования entity-компонентов и классического JDBC API для решения одинаковых задач. Надо сразу предупредить читателя, что сравнение не совсем корректное – entity-компоненты здесь будут выступать в роли «мальчиков для битья». Дело в том, что entity-компоненты обеспечивают явно избыточные возможности – по сравнению с теми задачами, которые решаются в тестовых примерах. В тестах не используется ни одна из «сильных» сторон entity-компонентов: кеширование данных в оперативной памяти между транзакциями, моделирование соотношений «один-ко-многим», «многие-к-одному» и других, гибкость настройки на различные базы данных.

Правильнее даже говорить не о сравнении этих способов – entity-компоненты (явно – в режиме BMP – или неявно – в режиме CMP) выполняют те же JDBC-операторы, которые присутствуют в варианте «чистого» JDBC. Таким образом, в тестах производится оценка того, сколько времени занимают дополнительные действия, выполняемые контейнером или entity-компонентом.

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

При тестировании в качестве инструмента использовались JBuilder 5 и Borland AppServer 4.5.1. Клиентские приложения во всех случаях запускались из среды JBuilder. Тестировались как «локальный» (клиент и сервер баз данных/сервер приложений находятся на одном компьютере), так и «удаленный» вариант. Borland Appserver и Interbase всегда работали на одном и том же компьютере...

**Полный текст статьи можно найти в печатной версии журнала

Copyright © 1994-2016 ООО "К-Пресс"